【转】接口幂等性设计

接口幂等性设计

  1. 场景再现:
  2. 1.提交表单,快速点击2次,除了id不同数据一样
  3. 2.为了解决接口超时而引入重试机制。第一次接口虽然超时但是成功了,重试再次成功,数据重复
  4. 3.MQ出现错误未及时提交消费信息,导致发生重复消费。
  5. ...
  1. 接口幂等性的效果:任意多次执行所产生的影响均与一次执行的影响相同
  1. 幂等性出现的场景:
  2. 1.增:有幂等性问题。这种情况下多次请求,可能会产生重复数据。
  3. 2.删:没有幂等性问题
  4. 3.改:有幂等性问题
  5. 如果只是单纯的更新数据,比如:update user set status=1 where id=1,是没有问题的;
  6. 如果还有计算,比如:update user set status=status+1 where id=1,这种情况下多次请求,可能会导致数据错误。
  7. 4.查:没有幂等性问题

1. insert前先select(并发不适用)

  1. ## 1.根据用户id查询数据
  2. select * from student where uid='12'
  3. ## 2.根据查询结果进行相应判断
  4. 如果存在则执行update操作,如果不存在执行则insert操作

2.加悲观锁(代码 或者 数据库)

  1. 2.1 代码种的悲观锁(lock 或者 synchronized
  2. 2.2 数据库的悲观锁(流程图见下图)
  3. 1.select * from user where id=123
  4. 2.select * from user where id=123 for update;
  5. 3.update user amount = amount-100 where id=123;

3.加乐观锁

  1. ## 1.查询数据时需要顺带查出版本号version
  2. select id,amount,version from user id=123;
  3. ## 2.更新数据查询条件带上版本号,并时顺带更新版本号+1
  4. update user set amount=amount+100,version=version+1 where id=123 and version=1;
  5. ## 3.判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

4.加唯一索引

  1. alter table `order` add UNIQUE KEY `un_code` (`code`);
  2. 注:因为数据库唯一索引冲突,所以代码里需要捕获异常才可以让程序返回成功,保证幂等性

5.使用防重表

  1. 4.1 防重表:可以只包含两个字段:id 唯一索引,唯一索引可以是多个字段比如:namecode等组合起来的唯一标识,例如:susan_0001
  2. 4.2 流程
  3. 1.用户通过浏览器发起请求,服务端收集数据。
  4. 2.将该数据插入mysql防重表
  5. 3.判断是否执行成功,如果成功,则做mysql其他的数据操作(可能还有其他的业务逻辑)
  6. 4.如果执行失败,捕获唯一索引冲突异常,直接返回成功。
  7. 注:防重表和业务表必须在同一个数据库 操作要在同一个事务中

6.状态机

  1. 1.很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,通过它可以保证接口的幂等性
  2. 2.例如:假如id=123的订单状态是已支付,现在要变成完成状态。
  3. update `order` set status=3 where id=123 and status=2;
  4. 3.流程:
  5. 3.1 用户通过浏览器发起请求,服务端收集数据。
  6. 3.2 根据id和当前状态作为条件,更新成下一个状态
  7. 3.3 判断操作影响行数,如果影响了1行,说明当前操作成功,可以进行其他数据操作
  8. 3.4 如果影响了0行,说明是重复请求,直接返回成功
  9. 注:状态机有点乐观锁的意思

7.加分布式锁

  1. Redis可以实现的3种分布式锁的方式
  2. 方式1setNx命令
  3. 方式2set命令
  4. 方式3Redission框架
  5. 注:内容过多,具体看我分布式专题

8.获取token

  1. 流程(2个操作保证幂等性):
  2. 操作1
  3. 1.服务端提供获取 Token 的接口,该 Token 可以是一个序列号,也可以是一个分布式ID或者UUID串。
  4. 2.客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。
  5. 3.然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
  6. 操作2
  7. 4. Token 返回到客户端,客户端拿到后应存到表单隐藏域中。
  8. 5.客户端在执行提交表单时,把 Token 存入到Headers中,执行业务请求带上该Headers
  9. 6.服务端接收到请求后从Headers中拿到 Token,然后根据 Token Redis 中查找该key是否存在。
  10. 7.服务端根据 Redis 中是否存该key进行判断。
  11. 如果存在就将该key删除,然后正常执行业务逻辑;如果不存在就抛异常,返回重复提交的错误信息。
  12. 注:在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。
  13. 其实现方法可以使用分布式锁 或者 使用Lua表达式来注销查询与删除操作

原文地址:https://www.cnblogs.com/yanghj010/p/14598904.html