一、定义

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。

这是来自百度百科的解释,讲人话幂等性就是指接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的

二、使用场景

对于查询的借口来说,可以天然的实现幂等性,因为不管查询多少次都是一样的结果。

而对于增删改的借口来说,我们也需要保证其接口的幂等性。

好比当用户多点击了几次创建商品的操作,这时候前端会重复请求一个新增的接口(要求后台执行一次创建),这时候后端重复创建了多条记录,这就是没有实现接口幂等性所带来的bug,也是我们需要避免的情况。

以下是接口幂等性可以使用的场景:

  1. 前端重复提交表单:在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
  2. 用户恶意进行刷单:例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
  3. 接口超时重复提交:很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
  4. 消息进行重复消费:当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

三、影响

幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:

  1. 把并行执行的功能改为串行执行,降低了执行效率。
  2. 增加了额外控制幂等的业务逻辑,复杂化了业务功能;

所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。

使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题

四、实现方式

  1. 唯一索引:利用主键唯一约束的特性,保证插入的数据只有一条

    • 该主键一般来说并不是使用数据库中的自增主键,而是使用分布式ID充当主键,这样才能保证分布式环境下ID的全局唯一性
  2. 防重Token令牌:集群环境采用token+redis(redis是单线程的,处理需要排队),单JVM环境采用token+jvm内存,针对客户端连续点击或调用方超时重试等情况

    • 调用接口前向后端申请token,token放入redis(作为key,用户信息作为value)或jvm内存中,并设置有效时间
    • 提交请求需携带token,后台验证是否存在token,以及value是否匹配,存在且匹配则请求成功并删除token,否则则请求失败
    • 执行redis的查找和删除存在并发问题,在并发情况下,执行 Redis 查找数据与删除需要保证原子性,可以通过使用分布式锁或Lua表达式来注销查询和删除操作。
  3. 状态机制幂等:状态变更, 更新数据时判断状态

    • 如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的
    • 电商订单,订单支付状态 0 待支付,1 支付中,3 支付成功,4 支付失败
    • update order set status = 1 where status =0 and orderId = “201251487987”
      该sql语句利用状态CAS 保证该操作的幂等
    • 比如要进行订单支付,上来先用CAS更新订单状态
      返回影响数为 1 代表修改成功,可以支付,继续执行支付业务代码
      返回影响数为 0 代表修改失败,该订单已经不是待支付订单了
  4. 悲观锁:获取数据时加锁获取

    • 在用户请求操作数据库前对表或行进行上锁,等完成操作后再将锁释放
    • 此时若有第二次请求企图访问数据库则会在尝试获取锁时失败
  5. 乐观锁:只是在更新数据时锁表,其他时间不锁表,所以相对于悲观锁,效率更高

    • 提前在对应的数据表中多添加一个字段,充当当前数据的版本标识
    • 每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值,保证一定更新的是某个版本对应下的信息
    • 重复更新时,在第二次更新的时候版本已经改变,故无法更新成功以实现幂等
  6. 分布式锁:通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁

    • 插入数据的例子,如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁
    • 某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供)
  7. 缓存队列:将请求放入队列,后续使用异步任务处理队列中的数据,过滤掉重复的消息。 和防止重复消费道理是一样

Q.E.D.


   七岁几胆敢预言自己,操一艘战机