防重幂等

前言:

在分布式系统下,服务之间相互调用,必然会存在调用失败并且进行重试的情况,在某些情况下就需要做好防重幂等。

防重和幂等是什么?

防重:避免产生重复数据

幂等:除了避免产生重复数据之外,还要求每次请求都返回一样的结果

什么情况会导致重复?

发送方发送相同的请求到服务端。

  • 前端多次发送相同的请求到后端
  • 超时重发导致的重复
  • MQ异常导致的重复消费

如何防重?

  • insert之前先select,通常情况下有效,但是在高并发情况下,也会导致重复

  • 建立唯一索引,数据库兜底,防止重复添加

  • 某些业务表在特定的场景下才不允许重复,不能直接建立唯一键,就可以增加一张防重表(为此类业务),将此类数据在同一事务下先insert进防重表成功,在insert业务表,假如insert进防重表失败,证明此类数据重复,就不用再处理业务表了

  • 加分布式锁(针对单据来锁):需要合理设置过期时间。不能太短,导致业务没有处理完,锁失效,防重失败;也不能不设置过期时间,解锁异常导致锁一直被占,阻塞后续处理。

什么情况要做幂等?

用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

例如:

  • 比如用户对一笔订单发起付款,因为网络问题没有返回结果,就多次点击付款按钮,此时只能发起一笔真实的交易,生成一条交易记录。
  • 分布式系统中,因为接口超时,导致的重试,第一次请求接口超时,没有获取到返回结果(有可能已经成功了),第二次重试,接收方不能直接返回失败,要根据第一次处理的结果进行返回。

怎么解决?

  1. 新增数据类接口,通过防重解决。
  2. 更新类接口,比如更新库存,更改状态等,通过状态,加乐观锁解决。

根据状态判断

很多业务是有状态的,比如一个订单表。有下单0、支付中1、已支付2、取消支付3等状态,

假如id=123的订单状态是0,现在要变成支付中状态。

update order set status=1 where id=123 and status=0;

第一次请求时,该订单的状态可以正常更新,sql执行结果的影响行数是1,订单状态变成了1。后面有相同的请求过来,再执行相同的sql时,由于订单状态变成了1,再用status=0作为条件,最终sql执行结果的影响行数是0,即不会真正的更新数据。但为了保证接口幂等性,接口也需要直接返回成功。

加乐观锁,在表中增加一个version字段。

在更新数据之前先查询一下数据:

select id,amount,version from user id=123;

如果数据存在,假设查到的version等于1,再使用id和version字段作为查询条件更新数据:

update user set amount=amount+100,version=version+1 where id=123 and version=1;

更新数据的同时version+1,然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:

update user set amount=amount+100,version=version+1 where id=123 and version=1;

该update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。

总结

  • 网络延迟问题:先发的不一定先到
  • 数据库操作延迟:先到的不一定先执行完
  • 不能依赖上游或下游去做防重幂等,自己本身也要把控好
  • 对于外部接口,没有明确返回可重试状态的,不要轻易重试
原文地址:https://www.cnblogs.com/jiezai/p/15499774.html