一、为什么需求幂等
- 分布式场景下,多个事务体系间完成强共同的协议是极其困难的。一个最简略和可完成的假设便是确保最终共同性,这要求服务端在处理一个重复的恳求时需求给出相同的回应,一起不会对持久化数据产生副作用(即屡次操作与单次操作的成果需求是事务视点共同的)。
- 一个API具有幂等才能的话,调用建议方就能够很安全的进行重试。这符合咱们遍及的假设。供给幂等才能是服务供给方有必要需求做的事。
- 具有幂等才能的话能够确保咱们的接口不会被各种反常重试或恶意恳求锁冲击。
二、幂等办法
不同的场景下(常见的是【界面和后端接口交互场景】和【接口于接口交互场景】),幂等办法有许多而且各不相同,都各有一些局限性和缺陷!!!
-
能够根据【
事务key + 事务状况机 + 达观锁
】去做幂等完成(一般适用于比较简略的update场景)- 比方:更新订单状况为
finished
的场景中,先依据订单号查询订单,判断订单状况是否为finished
,若不是则更新为finished
- 比方:更新订单状况为
-
能够根据【
事务key + 分布式锁 + 事务状况机
】去做幂等完成- 比方:新增用户信息场景中,履行办法前先加分布式锁(防并发),以用户身份证号为查询条件,查询用户,假如用户不存在则进行新增。假如用户存在则幂等处理
- 这种计划一般有个固定的流程:【
一锁、二判、三履行
】
-
能够根据【
事务key + 仅有索引
】去做幂等完成(一般适用于新增数据场景)- 比方:新增用户信息场景中,以用户身份证号为仅有键,建立仅有索引,新增用户时经过捕获仅有键冲突反常(
DuplicateKeyException
)进行幂等控制
- 比方:新增用户信息场景中,以用户身份证号为仅有键,建立仅有索引,新增用户时经过捕获仅有键冲突反常(
-
能够根据【
Redis + token形式
】去做幂等完成(多用于界面和接口交互,接口于接口交互不太适用,该计划也是比较常见的计划,但不在本次评论范围中)- 比方:用户提交填写好的表单信息,屡次重复提交时确保仅仅真正履行一次,其他的都幂等回来相同成果
-
能够根据【
幂等表
】去做幂等完成(比较通用的一种计划,详细功率取决于存储幂等记载的存储介质)- 比方:消费MQ音讯时,为了防止音讯重复消费,消费音讯前能够先插入一条幂等记载,然后再履行消费逻辑,消费完成后修正幂等记载的幂等状况为消费成功!
- 接口互调的状况类似
三、幂等规划原则
一个具有幂等性的服务,要求无论重复恳求在多么极点的状况下产生,都要表里如一,此刻有必要满意:
- 对外:回来彻底相同的成果
- 对内:自身状况不再产生任何改变
-
对于服务供给方来说:严厉来说需求恳求中的字段彻底一样,服务供给刚才以为是重复恳求。可是在实践环境中咱们或许没有这么严厉的要求,咱们一般以为只要关键的事务参数相同,那么他就归于重复恳求,应该被幂等处理。
-
对于服务调用方来说:需求做好幂等成果处理,屡次恳求回来相同成果需求正确被处理
-
幂等规划要尽量从
简略、牢靠、高效
(过多的幂等逻辑会对可用性和性能形成影响)视点动身- 简略:幂等流程和逻辑要尽量简略
- 牢靠:不仅仅在正常运转的状况下要确保幂等的牢靠性,在某些反常场景下也要尽量确保幂等的牢靠性,不然该幂等规划的意义将大打折扣
- 高效:幂等逻辑履行不能高耗时,针对于一些高并发的接口需求做到尽量削减幂等逻辑履行耗时
-
通用幂等组件规划易用性和可扩展性也同样重要
四、常见幂等场景比方
- 【
MQ音讯消费场景中
】,因为MQ为了确保音讯投递成功,或许会建议屡次重试,那么顾客方便需求确保重复的音讯能够被幂等处理(比方:监听用户支付成功音讯进行生成支付单) - 【
界面和接口交互场景中
】,前端重复提交数据,后台接口需求确保只履行一次,其他重复恳求均幂等回来(比方:用户重复提交订单、重复提交录入的用户信息) - 【
接口互调的场景中
】,调用方或许因为多种原因没能收到响应成果而建议重试(比方:数据同步、库存扣减等),此刻被调用方需求确保重复调用幂等处理
五、幂等实践
- 为了将幂等这个常见的通用需求尽量规划得通用化,咱们这里选用【
幂等表 + 幂等状况机
】来完成,该计划能够适用于绝大部分的【界面 + 接口交互】和【接口 与 接口交互】形式 - 假如项目仅仅是界面和接口交互形式,那么选用【
Redis + token
】计划也是一个不错的挑选 - 当然,软件工程中几乎没有银弹,很难有一种完美适用与所有场景的计划
1、规划流程
-
调用放建议恳求,恳求抵达服务供给方
-
获取指定的事务key作为仅有的幂等键,构建幂等记载(此刻幂等记载status为处理中(
processing
)),然后测验将幂等记载写入存储介质(可所以Redis也可所以MySQL或其他存储介质) -
假如幂等记载写入成功,则履行事务逻辑
-
事务逻辑履行完毕,经过仅有键修正幂等记载的status为成功(
success
) -
假如幂等记载写入失利,则阐明幂等记载已存在(该事务key对应的数据,之前有被履行过),需求进行如下处理:
-
经过幂等仅有键查询幂等记载,而且断定幂等记载的status
-
假如status为成功(
success
),则阐明前次现已履行过该事务了,本次无需再重复履行,获取前次履行的成果(假如有需求的话)幂等回来即可 -
假如status为处理中(
processing
),则阐明现已有其他线程正在处理事务数据 或者是 极点状况下使用宕机导致的反常状况。此刻需求断定【恳求处于处理中(processing)状况的时长
】,而且结合使用装备的【答应的最大事务履行时长
】进行判断- 处于
processing
状况的时刻现已超越装备的【答应的最大事务履行时长
】,则测验以达观锁的办法重新修正幂等记载,假如修正成功则履行事务逻辑,反之则抛出并发反常。 - 处于
processing
状况的时刻没有超越装备的【答应的最大事务履行时长
】,那么直接抛出并发恳求反常
- 处于
-
2、问题思考
关于上述的幂等完成流程中,极点状况下,有如下几点需求思考和注意的问题点
-
极点状况下,假如插入幂等记载成功,而且正常履行了事务流程,此刻更新幂等状况为success时出现反常(比方存储幂等记载的存储介质宕机了),此刻是否需求处理该反常,还是说抛出反常中断流程???假如抛出反常会有什么影响?假如catch反常会有什么影响?
- 计划一、抛出反常:假如抛出反常中断流程,那么调用方应该感知到调用失利了,可是实践上事务流程现已履行完毕,这种状况假如调用方建议重试,那么幂等便会失效(同一个事务code被履行了两次)
- 计划二、不抛出反常:假如不抛出反常,接口会持续履行,然后回来数据给调用方,假如调用方收到了回来数据,那么便不会建议重试了,不会有幂等问题。可是此刻幂等记载的状况仍然是处理中(
processing
),再指定了事务最大履行时刻
的状况下,假如调用方【超越指定的最大履行
】时刻再次建议重试,那么幂等仍然失效(当然咱们能够不指定事务最大履行时刻) - 经过上述两种状况的比较,咱们一般倾向第二种计划,自己处理掉反常,而且做一层 【兜底战略】 (比方告警或记载该条幂等数据信息等,后续能够转人工核对该数据),这种计划愈加稳定和适用
-
假如事务逻辑履行失利,那么是否应该删去之前创建的幂等记载?
-
依照严厉的幂等意义来说,咱们应该保存这条幂等记载,而且将幂等记载的状况修正为
Exception或failed
,后续有重试恳求进来时履行回来failed给调用方即可(确保屡次调用得到的成果相同)。- 可是在真实环境中事务履行反常有或许是数据校验失利、接口里调用外部体系失利(比方外部体系正在发版(没有做高雅发布)等)
- 针对于这些状况或许调用方修正数据后进行重试或过必定时刻后进行重试,那么此刻最好有必定的自愈才能,而不是每次这种数据都转人工处理(一些场景中会加大人力成本,比方我之前涉及到的某个体系,常常有些调用方传递的事务参数有问题或接口里调用外部体系失利的状况)
- 当然这两种战略需求依据详细的状况来挑选,没有谁好谁坏之分。
-
-
是否需求设定【最大的处理时刻】,比方咱们希望接口最大处理时刻为1小时(也便是说幂等记载处理processing状况的时刻最大为1h),假如超越这个时刻,那么以为这不是一种正常的case,下次重试恳求时应该测验康复事务履行。
- 这也是个具有两面性的挑选问题,需求依据实践项目状况权衡挑选
3、幂等完成
根据幂等表的幂等计划完成:gitee.com/mr_wenpan/b…