定义

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

背景

单体应用拆成微服务架构后,老是出现数据不一致的问题。

原因

接口幂等性 - 图1对于业务中需要考虑幂等性的地方一般都是接口的重复请求。

重复请求是指同一个请求因为某些原因被多次提交。导致这个情况会有几种场景:

  • 前端重复提交:提交订单,用户快速重复点击多次,造成后端生成多个内容重复的订单。
  • 接口超时重试:对于给第三方调用的接口,为了防止网络抖动或其他原因造成请求丢失,这样的接口一般都会设计成超时重试多次。
  • 消息重复消费:MQ消息中间件,消息重复消费。

因此我们必须给调用接口提供幂等性保证,防止重复调用出现不一致的情形!

什么是接口幂等性

幂等性强调的是外界通过接口对系统内部的影响,一次或多次调用对某一个资源应该具有同样的副作用就行,但是返回值允许不同。
用增删改查来举例。

(1)查询

查询操作并不会产生或变更新的数据,因此查询是天然具备幂等性。

比如,查询接口的sql语句:

  1. select * from table;

此时,有一个线程,一直往这个table插入数据,那你每次调用查询接口的返回值肯定不一样。但是,不能说查询操作不熟幂等性操作。

(2)删除

分为物理删除逻辑删除

  • 物理删除:删除只会进行一次,无论执行几次delete,造成的效果一样,是幂等性操作。
  • 逻辑删除:用update修改字段,无论update几次,造成的效果一样,是幂等性操作。

    (3)增加

    要看这张表是否带唯一索引。

  • 带唯一索引insert:重复插入时,会插入失败,是幂等性操作

  • 不带唯一索引insert:非幂等性操作。

    (4)修改

  • 计算式update:update table set number=number-1 where id=1; 非幂等性操作。

  • 非计算式update:update table set number=3 where id=1; 幂等性操作。

只有不带唯一索引Insert计算式Update会引起幂等性问题的。

解决方案

token + redis

1162587-20200701142807385-1283194170.png

流程:

  • 客户端先发送获取token的请求,服务端会生成一个全局唯一的ID保存在redis中,同时把这个ID返回给客户端。
  • 客户端调用业务请求的时候必须携带这个token,一般放在请求头上。
  • 服务器判断token是否存在redis中,存在,表示是第一次请求,这时把redis中的token删除,继续执行业务。
  • 如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

    数据库去重表

    往去重表里插入数据的时候,利用数据库的唯一索引特性,保证唯一的逻辑。唯一序列号可以是一个字段,例如订单的订单号,也可以是多字段的唯一性组合。

使用数据库防重表的方式它有个严重的缺点,那就是系统容错性不高,如果幂等表所在的数据库连接异常或所在的服务器异常,则会导致整个系统幂等性校验出问题。

Redis

Redis实现的方式就是将唯一序列号作为Key。
唯一序列号也可以是一个字段,例如订单的订单号,也可以是多字段的唯一性组合。
当然这里需要设置一个 key 的过期时间,否则 Redis 中会存在过多的 key。
1162587-20190526224227719-1701958206.png

状态机

对于很多业务是有一个业务流转状态的,每个状态都有前置状态和后置状态,以及最后的结束状态。例如流程的待审批,审批中,驳回,重新发起,审批通过,审批拒绝。订单的待提交,待支付,已支付,取消。

以订单为例,已支付的状态的前置状态只能是待支付,而取消状态的前置状态只能是待支付,通过这种状态机的流转我们就可以控制请求的幂等。
image.png
假设当前状态是已支付,这时候如果支付接口又接收到了支付请求,则会抛异常或拒绝此次处理。