2.幂等设计
1.定义及场景
定义:一次和多次请求某一个资源具有相同的副作用;
场景:系统解耦后,服务之间的调用可能有三种状态:成功(success)\失败(fail)\超时未知(timeout),前两种结果是明确的。但是第三种的超时则完全不知道是什么状态。
对于第三种情况,一般有两种解决办法:
一:提供相应的查询接口,根据得到的查询结果(成功\失败)去执行相应的流程。[这种方式存在的问题在于下游还没有处理完,得到的结果仍然是未知的或者是初始状态,没有达到終态,需要和下游约定好多长时间以后才可以去查询]
二:让上游根据拿原单号进行幂等重试,下游系统保证一次请求和多次请求的结果是一致的。
2.幂等设计的原则
方案一
当收到交易请求的时候,我们就会到这个存储中去查询。如果查找到了,那么就不再做处理了,并把上次处理的结果返回。如果没有查到,那么我们就继续执行流程。
这个的问题在于:绝大多数的请求都不是重复请求来的,但是这样的流程是100%去查询,会增加系统的耗时;
image.png
方案二
对于插入的场景:当这个存储出现冲突的时候会报错。也就是说,我们收到交易请求后,直接去存储里记录这条交易信息(相对于数据的 Insert 操作),如果出现冲突的异常,那么我们就知道这个之前已经有人发过来了,所以就不用再做了。比如,数据库中你可以使用 insert into … values … on DUPLICATE KEY UPDATE … 这样的操作。
对于更新的场景:如果只是状态更新,可以使用如下的方式。如果出错,要么是非法操作,要么是已被更新,要么是状态不对,总之多次调用是不会有副作用的。update table set status = “paid” where id = xxx and status = “unpaid”;
3.业务场景分析
3.1 幂等最初的方案
- 请求受理,从DB层面(利用数据库的唯一性索引)判断是否重复;从缓存层面判断是否重复;无重复跳到步骤2,重复跳到步骤3
- 无重复,幂等校验通过,继续执行后续的业务流程;
- 重复抛异常,外层捕获,此时要进行关键参数的比对(根据数据库的参数和请求的参数关键信息进行比对),如果相同,构造相同的成功结果返回;如果不相同,交易信息被篡改,抛出违反唯一性约束的异常;
3.2 当前场景存在的问题
1. 为什么使用DB+缓存的双重幂等措施?
DB存在容灾的情况,极端情况下,一笔请求在主库,第二笔请求由于容灾请求到了容灾库,幂等失效;即该方式只在单库单表的情况下有效;
缓存的设计:
- 当一笔请求进来时,先根据外部交易号去MySQL查询,不存在的话写入缓存;写入成功后,缓存发出异步复制的消息,Mysql存储幂等信息(落地该消息)
缓存设计存在的问题: - 发生容灾后,缓存的权重发生变化,写入不同的分区,第一层幂等被击穿;
- MySQL数据异步复制需要时间,此时还没有复制成功,第二层幂等同样失效;
目前没有百分百完全可靠的幂等方案。
2.当前幂等方案的优化
2.1 关键参数比对
- 第一个注意点:关键参数一定不能使用可变参数,比如时间、一个hashmap(遍历时无效)
- 特殊场景的关键性参数其实不一样的,甚至可以不同(比如收款方、支付工具)
2.2 识别幂等的处理方式多样化
- 某些场景下,识别幂等直接返回结果,还有某些场景识别后需要继续往下面执行;
2.3 幂等返回结果的调整
- "支付成功"VS"已经支付成功"