场景再现:
1.提交表单,快速点击2次,除了id不同数据一样
2.为了解决接口超时而引入重试机制。第一次接口虽然超时但是成功了,重试再次成功,数据重复
3.MQ出现错误未及时提交消费信息,导致发生重复消费。
...
接口幂等性的效果:任意多次执行所产生的影响均与一次执行的影响相同
幂等性出现的场景:
1.增:有幂等性问题。这种情况下多次请求,可能会产生重复数据。
2.删:没有幂等性问题
3.改:有幂等性问题
如果只是单纯的更新数据,比如:update user set status=1 where id=1,是没有问题的;
如果还有计算,比如:update user set status=status+1 where id=1,这种情况下多次请求,可能会导致数据错误。
4.查:没有幂等性问题
1. insert前先select(并发不适用)
## 1.根据用户id查询数据
select * from student where uid='12'
## 2.根据查询结果进行相应判断
如果存在则执行update操作,如果不存在执行则insert操作
2.加悲观锁(代码 或者 数据库)
2.1 代码种的悲观锁(lock 或者 synchronized)
2.2 数据库的悲观锁(流程图见下图)
1.select * from user where id=123
2.select * from user where id=123 for update;
3.update user amount = amount-100 where id=123;
![](https://gitee.com/domineering_red_tide/image/raw/master/image/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_16171038012171.png)
3.加乐观锁
## 1.查询数据时需要顺带查出版本号version
select id,amount,version from user id=123;
## 2.更新数据查询条件带上版本号,并时顺带更新版本号+1
update user set amount=amount+100,version=version+1 where id=123 and version=1;
## 3.判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。
4.加唯一索引
alter table `order` add UNIQUE KEY `un_code` (`code`);
注:因为数据库唯一索引冲突,所以代码里需要捕获异常才可以让程序返回成功,保证幂等性
5.使用防重表
4.1 防重表:可以只包含两个字段:id 和 唯一索引,唯一索引可以是多个字段比如:name、code等组合起来的唯一标识,例如:susan_0001。
4.2 流程
1.用户通过浏览器发起请求,服务端收集数据。
2.将该数据插入mysql防重表
3.判断是否执行成功,如果成功,则做mysql其他的数据操作(可能还有其他的业务逻辑)
4.如果执行失败,捕获唯一索引冲突异常,直接返回成功。
注:防重表和业务表必须在同一个数据库 且 操作要在同一个事务中
![](https://gitee.com/domineering_red_tide/image/raw/master/image/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_16171000948134.png)
6.状态机
1.很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,通过它可以保证接口的幂等性
2.例如:假如id=123的订单状态是已支付,现在要变成完成状态。
update `order` set status=3 where id=123 and status=2;
3.流程:
3.1 用户通过浏览器发起请求,服务端收集数据。
3.2 根据id和当前状态作为条件,更新成下一个状态
3.3 判断操作影响行数,如果影响了1行,说明当前操作成功,可以进行其他数据操作
3.4 如果影响了0行,说明是重复请求,直接返回成功
注:状态机有点乐观锁的意思
7.加分布式锁
Redis可以实现的3种分布式锁的方式
方式1:setNx命令
方式2:set命令
方式3:Redission框架
注:内容过多,具体看我分布式专题
8.获取token
流程(2个操作保证幂等性):
操作1:
1.服务端提供获取 Token 的接口,该 Token 可以是一个序列号,也可以是一个分布式ID或者UUID串。
2.客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。
3.然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
操作2:
4.将 Token 返回到客户端,客户端拿到后应存到表单隐藏域中。
5.客户端在执行提交表单时,把 Token 存入到Headers中,执行业务请求带上该Headers。
6.服务端接收到请求后从Headers中拿到 Token,然后根据 Token 到 Redis 中查找该key是否存在。
7.服务端根据 Redis 中是否存该key进行判断。
如果存在就将该key删除,然后正常执行业务逻辑;如果不存在就抛异常,返回重复提交的错误信息。
注:在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。
其实现方法可以使用分布式锁 或者 使用Lua表达式来注销查询与删除操作
![](https://gitee.com/domineering_red_tide/image/raw/master/image/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_16171043145085.png)
posted @
2021-03-30 21:14
我只吃大碗
阅读(
291)
评论()
编辑
收藏
举报