文章目录
- 如何设计幂等
- 幂等设计的基本流程
- 建防重表
- 根据状态机
- 分布式锁
- 获取 token
如何设计幂等
既然这么多场景需要考虑幂等
,那我们如何设计幂等呢?
幂等意味着 一条请求的唯一性。不管是你哪个方案去设计幂等,都需要一个
全局唯一的ID
,去标记这个请求是独一无二的。
- 如果你是利用
唯一索引
控制幂等,那唯一索引是唯一的
- 如果你是利用
数据库主键控制
幂等,那主键是唯一的
- 如果你是
悲观锁
的方式,底层标记还是全局唯一的ID
全局的唯一性ID
uuid–redis自增–雪花算法等
幂等设计的基本流程
过滤一下已经收到的请求
,当然,请求一定要有一个全局唯一的ID
标记哈。
然后,怎么判断请求是否之前收到过呢?把请求储存起来,收到请求时,先查下存储记录,记录存在就返回上次的结果,不存在就处理请求
建防重表
有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景
才不允许。这时候,直接在表中加唯一索引
,显然是不太合适的。
针对这种情况,我们可以通过建防重表
来解决问题。
该表可以只包含两个字段:id 和 唯一索引
,唯一索引可以是多个字段比如:name、code 等组合起来的唯一标识
,例如:susan_0001
。
具体流程图如下
具体步骤:
- 用户通过浏览器发起请求,服务端收集数据。
- 将该数据插入
mysql 防重表
- 判断是否执行成功,如果成功,则做 mysql 其他的数据操作(可能还有其他的业务逻辑)。
- 如果执行失败,捕获唯一索引冲突异常,直接返回成功。需要特别注意的是:防重表和业务表必须在同一个数据库中,并且操作要在
同一个事务中
根据状态机
很多时候业务表是有状态
的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销
等状态。
如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性
。
假如 id=123 的订单状态是已支付
,现在要变成完成状态。
update `order` set status=3 where id=123 and status=2;
第一次请求时,该订单的状态是已支付
,值 2
,所以该 update 语句可以正常更新数据
,sql 执行结果的影响行数是 1
,订单状态变成3
后面有相同的请求过来,再执行相同的 sql 时
,由于订单状态变成了 3
,再用 status=2
作为条件,无法查询出需要更新的数据
,所以最终 sql 执行
结果的影响行数是 0
,即 不会真正的更新数据。但为了保证接口幂等性,
影响行数是 0
时,接口也可以直接返回成功。
具体流程图如下:
具体步骤:
- 用户通过浏览器发起请求,服务端收集数据。
- 根据 id 和当前状态作为条件,更新成下一个状态
- 判断操作影响行数,如果影响了 1 行,说明当前操作成功,可以进行其他数据操作。
- 如果影响了 0 行,说明是重复请求,直接返回成功。
主要特别注意的是,该方案仅限于要更新的表有状态字段
,并且刚好要更新
状态字段的这种特殊情况,并非所有场景都适用
。
分布式锁
防止重复下单(redis+数据库唯一索引requestId实现幂等)
获取 token
springboot + redis + 注解 + 拦截器用Token 实现接口幂等性
使用 token
的方案。该方案跟之前的所有方案都有点不一样,需要两次请求才能完成一次业务操作。
- 第一次请求获取 token
- 第二次请求带着这个 token,完成业务操作
具体步骤:
- 用户访问页面时,浏览器自动发起获取 token 请求。
- 服务端生成 token,保存到 redis 中,然后返回给浏览器。
- 用户通过浏览器发起请求时,携带该 token。
- 在 redis 中查询该 token 是否存在,如果不存在,说明是第一次请求,做则后续的数据操作。
- 如果存在,说明是重复请求,则直接返回成功。
- 在 redis 中 token 会在过期时间之后,被自动删除。
以上方案是针对幂等设计
的
如果是防重设计
,流程图要改改
需要特别注意的是:
token 必须是全局唯一的