什么是幂等性

什么是幂等性?一次和多次请求某一个资源,对资源本身所产生的的影响均与一次执行的影响相同。

幂等性是系统服务对外的一种承诺,承诺只要调用接口成功了,多次调用对系统的影响是一致的。

幂等性与重复提交比较

幂等性 更多使用的情况是第一次请求知道结果,但是由于网络抖动或连接超时等情况未进行正常返回,在这种情况下系统自动再次发起请求,其目的是确认第一次是否请求完成。

重复提交 更多使用的情况是第一次请求成功或请求结果暂未返回的情况下,人为的进行多次操作。

为什么需要保证接口的幂等性?

接口的幂等性非常重要,因为在实际的应用中,接口可能会被调用多次,例如在网络不稳定或者客户端重试的情况下。如果接口没有保证幂等性,那么就会导致数据或者业务逻辑出现不一致的情况。

例如,如果一个非幂等性的接口被调用两次,第一次调用时创建了一个资源,第二次调用时又创建了一个相同的资源,这样就会导致资源数量不一致。

哪些场景需要保证幂等性

前端重复提交表单:在填写一些表单数据的时候,用户点击提交按钮,但是由于网络波动导致服务端没有及时给用户返回提交成功的响应,致使用户认为没有成功提交而重复点击提交按钮,这样就会重复提交表单数据。

超时重试机制:在分布式架构中,由于引入了网络通信导致一个请求除了成功和失败以外还多了一个未知的状态,也就是有可能这次请求在服务端执行成功了,由于网络故障等原因,客户端在一定时间内没有收到服务端的响应,于是客户端为了保证这次操作的成功会发起一个重试操作,导致同一个接口被重复调用了多次。

MQ消息重复消费:正常情况下,消费者在消费消息的时候,消费完毕后会发送一个确认的信号(ACK)给消息队列,消息队列在收到确认信号(ACK)的时候就知道这条消息已经被成功消费了,于是就会把这条消息从消息队列中删除,但是可能由于网络波动等故障,这个确认的信号(ACK)没有传送到消息队列,导致消息队列会认为这条消息没有消费成功,于是消息队列会把这条消息发送给其他的消费者去消费,这样就导致消息被重复消费了。

SQL 语句幂等性

SELECT

1
SELECT * FROM `user` WHERE id = 1

无论执行多少次都不会对资源造成影响,查询具有天然的幂等性。

UPDATE

1
UPDATE `user` SET status = 1 WHERE id = 1

无论执行成功多少次状态都是一致的,这种场景是幂等操作。

1
UPDATE `user` SET score = score+1 WHERE id = 1

每次执行的结果都会发生变化,这种场景不是幂等操作。

根据具体场景看能否写成这样的 SQL

1
UPDATE `user` SET score = score+1 WHERE id = 1 AND score = 59

无论执行成功多少次分数都是一致的,这种场景是幂等操作。

DELETE

1
DELETE FROM `user` WHERE id = 1

无论执行成功多少次数据都是一致的,这种场景是幂等操作。

INSERT

1
INSERT INTO `user` (`name`, `status`, `score`) VALUES ('tom', 1, 80)

每次执行的结果都会发生变化,这种场景不是幂等操作。

根据具体场景看能否为 name 创建一个唯一索引,或执行类型这样的 SQL

1
2
INSERT INTO ... values ... ON DUPLICATE KEY UPDATE ...
// 注意,要使用这条语句,前提条件是这个表必须有一个唯一索引或主键。

实现方案

以下是保证接口幂等性的几种常见方法。

1. 唯一标识符

为了保证接口的幂等性,我们可以为每个请求生成一个唯一标识符。可以使用UUID或其他类似的方法生成这个标识符。这个标识符可以在服务器端存储,以确保同一请求只被处理一次。

在客户端发送请求时,我们需要将这个唯一标识符一起发送到服务器端。服务器端接收到请求后,可以检查这个唯一标识符是否已经处理过。如果已经处理过,就不再处理这个请求,而是直接返回结果。

2. 数据库唯一索引

适用于插入操作

在设计表的时候我们可以规定一些在业务上唯一的字段(比如:身份证号、订单号),为这些字段建立一个唯一索引。在做插入操作的时候,第一次请求的数据可以插入成功。但后面的相同请求,插入数据时会报 Duplicate entry 'xxx' for key 'xxxxxxx' 异常,表示唯一索引有冲突。

缺点:

效率不高,只支持插入操作,并且高并发下数据库压力比较大

3. 乐观锁

乐观锁是一种并发控制的方法。在Web开发中,乐观锁可以用来保证接口的幂等性。具体实现方法如下:

  • 当客户端发送请求时,服务器端首先读取当前资源的版本号。
  • 服务器端根据客户端请求更新资源,并生成新的版本号。
  • 服务器端将新的版本号返回给客户端。

如果客户端重复发送请求,服务器端会检查请求中的版本号是否与当前资源的版本号相同。如果相同,说明这个请求已经被处理过,服务器端不会再次处理请求,直接返回结果。

4. Token

幂等性Token是一种用来保证接口幂等性的标识符。当客户端发送请求时,服务器端会为这个请求生成一个幂等性Token。这个Token可以在服务器端存储,以确保同一请求只被处理一次。

在客户端发送请求时,需要将这个幂等性Token一起发送到服务器端。服务器端接收到请求时,会检查这个Token是否已经处理过。如果已经处理过,就不再处理这个请求,而是直接返回结果。

5. Redis+Token

redis-token

6. 状态机

有的业务表是有状态的,可以根据状态字段保证接口的幂等性。

比如在云数据库管控系统中,数据库实例的状态有(初始化、运行中、变配中)等状态,当一个请求发起实例变配时,会首先去查询表中该实例的状态,只有在运行中状态的实例才可以执行成功,并把状态改为变配中,此时如果有重复的请求过来,查询到状态是变配中,直接返回,不允许操作。

7. 防重表

有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景才不允许。这时候,直接在表中加唯一索引,显然是不太合适的。

针对这种情况,可以通过建防重表来解决问题。

该表可以只包含两个字段:id 和 唯一索引,唯一索引可以是多个字段比如:name、code等组合起来的唯一标识,例如:gouri_123