幂等概念和场景
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
用通俗的话讲:就是针对一个操作,不管做多少次,产生效果或返回的结果都是一样的。
举例如下:
-
比如前端对同一表单数据的重复提交,后台应该只会产生一个结果。
-
比如我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发或系统bug重发,也应该只扣一次钱。
-
比如发送消息,也应该只发一次,同样的短信如果多次发给用户,用户会崩溃。
-
比如创建业务订单,一次业务请求只能创建一个,不能出现创建多个订单。
-
内部系统MQ消息消费时,由于网络原因,发生消息重新消费情况,不能出现多笔交易记录
还有很多诸如此类的,这些逻辑都需要幂等的特性来支持。
常用解决方案
token(两次请求)
针对前端页面重复提交请求,这种情况,通常客户端,服务端都需要做限制
- 前端做防重复提交限制(如提交后按钮灰掉),仅仅是前端限制是远远不够,(任何时候不要相信前端请求,有被篡改的风险)
- 服务端同样需要做点工作,一般采取token机制:
- 前端发起获取token请求,服务端生成token,并放在缓存,并返回
- 客户端业务请求+token 提交至服务端,服务端校验token ,判空token存在则放入请求,并删除token(一个token只处理一个请求),token不存在则丢弃请求(说明请求重复)
数据去重表(select+insert)
在并发不高的系统,建立数据去重表,可以设置流水号字段为唯一索引 ,要求客户端请求中须带上唯一请求流水号,在实际业务处理前,先查询下流水号是否已经存在,如已经存在,则请求重复,需要丢请求,如不存在则可以进行正常业务处理。注意:高并发流程不要用这种方法;
状态机(状态机不可逆)
这种方法适合在有状态机流转的情况下,比如订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99,在做状态机更新时,我们就这可以这样控制,保证数据只更新一次。
update `order` set status=#{status} where id=#{id} and status<#{status}
分布式锁(加锁防重)
在业务系统插入数据或者更新数据,可通过获取分布式锁(如UID+流水号为唯一键),然后做业务逻辑操作,完成后释放锁, 即保证一时间该流程只能有一个线程获得锁,能正常执行业务,执行完成后,释放分布式锁,未获取到锁的请求,则丢弃
乐观锁(版本号+状态机或其他条件)
乐观锁只是在更新数据那一刻锁表,其他时间不锁表, 通过版本号实现
update table_xxx set name=#name#,version=version+1 where version=#version# and xx>0
仅针对更新操作,确保同一请求只更新一次
分布式缓存(REDIS+唯一流水号)
考虑到数据去重表 并发性不高,我们可请求流水号数据放在分布式缓存中,设置一定时间过期时间,这样可以极大的提升系统吞吐量。然而,毕竟缓存会过期,针对请求数据敏感的业务,还是要把请求数据持久化到DB中,这样可确定数据稳定可靠
对外部API设计(DB+唯一流水号)
insert,update类的接口, 一定要保证幂等,请求流水号一定要有且唯一,一般采用数据库去重的做法,且请求数据一定要落库,确保数据完整可靠
上述解决方案如果能帮到您,记得帮忙点个赞~~~ 关注小虎暮雨,一起学习,一起成长~