幂等设计
幂等性:不管是执行一次还是多次,产生的结果和影响都是相同的
如:保证相同的ID组合只会插入一条数据到DB里面,如果一个请求是save,后续的都应该update这条数据
属于具有幂等性的SQL:
//指定查询
select opcd from info_oper where opid='000000'
//指定更新
if exists(select 1 from info_oper where opid='000000')
begin
update info_oper set opcd='9999' where opid='000000'
end
//指定删除
if exists(select 1 from info_oper where opid='000000')
begin
delete from info_oper where opid='000000'
end
幂等的缺点:——因此,除了业务上的特殊要求外,尽量不提供幂等的接口
-
使客户端逻辑处理变得简单,但服务逻辑却变得复杂
-
把并行执行的功能改为串行执行,降低了执行效率
满足幂等服务的需要在逻辑中至少包含两点:
- 先查上一次的执行状态,如果没有则认为是第一次请求
- 在服务改变状态的业务逻辑前,保证防重复提交的逻辑
遇到的具体业务实例:上线sql、hangfire定时删除数据
需要使用幂等的情况:业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而造成重复提交情况。具体如:用户在APP上连续点击了多次提交订单,后台应该只产生一个订单;支付宝发起支付请求,由于网络问题或系统BUG重发,支付宝应该只扣一次钱等
分布式系统中一定要考虑幂等的设计,因为相较于进行分布式事务设计,幂等设计轻量级的多。
- 如果接入的是服务端,可以由服务端确保生成唯一的标识符
- 如果是接入最终用户的浏览器,则可以由自己的服务器先生成一个标识符发送给浏览器,当用户提交表单的时候,以此来认证是否为二次提交。
- 如果确认为二次提交,则把第一次的处理结果再次返给请求端
常用的保证幂等的手段:
-
MVCC方案
多版本并发控制,该策略主要使用update with condition(更新带条件来防止)来保证多次外部请求调用对系统的影响是一致的。在系统设计的过程中,合理的使用乐观锁,通过version或者updateTime(timestamp)等其他条件,来做乐观锁的判断条件,这样保证更新操作即使在并发的情况下,也不会有太大的问题。
-
去重表——利用唯一索引
在插入数据的时候,插入去重表,利用数据库的唯一索引特性,保证唯一的逻辑
-
悲观锁
select for update,整个执行过程中锁定该订单对应的记录。但值得注意的是:在数据库读大于写的情况下尽量少用
-
select+insert
并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了。注意:核心高并发流程不要用这种方法。
-
状态机幂等
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。
-
Token(令牌)机制 – (ToKen:令牌(临时),一般作为邀请、登录系统使用)
业务要求:页面的数据只能被点击提交一次
发生原因:由于重复点击或者网络重发,或者nginx重发(HTTP和反向代理服务器,支持容错和负载均衡)等情况会导致数据被重复提交
解决办法:
- 集群环境:采用token加redis(redis单线程的,处理需要排队)
- 单JVM环境:采用token加redis或token加jvm内存
处理流程:
- 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间
- 提交后后台校验token,同时删除token,生成新的token返回
token特点:要申请,一次有效性,可以限流
提交订单时保证幂等的方法:
使用去重表的方法:
1.后端创建一个流水表用来暂存用户下单请求信息(主键是有前端发送创建订单请求时发送给后端的流水ID)
2.在用户从前端确认下单的时候生成一个唯一流水ID,连同订单信息一起请求发送给后端,在用户未收到后端的反馈信息之前前端界面应该处于等待状态(这时候流水ID不变),当收订单生成成功的反馈信息后,清空流水ID;当出现网络异常,订单生成失败的时候,应给出生成订单失败的提示,这时若用户停留在当前界面重新提交订单,流水ID不变,直至用户放弃提交订单,返回或跳转至其他界面,清空或者更新流水ID