谈谈我对防重和幂等的一些思考

299 阅读4分钟

防重是指对请求或消息在一定时间内进行去重,避免产生重复数据。

幂等是保证请求或消息在任意时间内进行处理,需要保证他的结果是一致的。

结合我工作的场景来谈谈防重

如何防止重复下单?

对于我们的系统来说,下单分为三个步骤,是对不同业务系统进行的。

  • 第一次是对于我们系统来说的下单,这时候开始生成我们的订单表
  • 第二次对于XXX业务中台的下单
  • 第三次是对第三方支付渠道下单比如微信、支付宝,不过我们系统是对接我们公司的支付平台的,实际是由公司的支付平台对第三方支付渠道进行下单

第一次本系统下单利用****数据库唯一主键

我们利用redis生成唯一ID作为我们系统订单表的主键,当经过某上传明细业务逻辑后,我们开始对订单开始进行落盘,这里利用数据库唯一主键来防止重复下单。

第二次XXX业务中台下单利用****token机制

请求XXX业务中台是需要支付paytoken的,在其他流程我们获得这个支付paytoken后会进行对业务中台下单,每个支付paytoken只能对应一个订单,如果外部系统请求XXX业务中台下单的时候应该paytoken进行多个下单操作会报错。

第三次是对第三方支付渠道进行下单

经过前面两个步骤,我们基本已经确认我们需要第三方支付渠道需要支付多少钱。所以我们请求第三方支付渠道进行下单。我们作为上游系统一定要判断好是否重新进行下单。

因为第一次下单后我们已经有了我们系统的订单表并且订单表ID是唯一的。那么此订单号就可以作为我们后续操作的基点。

我们利用分布式锁锁住订单号判断该订单号是否已进入校验环节,如果已经进入校验环节则不能进行重复下单。

第二我们也可以通过订单号判断该订单的状态,如果该状态是不可重试状态也是不能进行重复下单。

结合我工作的场景来谈谈幂等

我感觉幂等的处理流程是查询请求的唯一ID是否存在该记录,如果有,判断其状态,根据具体业务去进行不同的操作!

唯一的订单号

用户支付成功后我们的系统会进行校验环节。系统的校验环节会遭受到多方的请求。所以我们利用分布式锁来保证同一时间内并发的数据安全性。比如:第三方渠道通知,前端页面的查询结果,RocketMQ或Redis延迟队列的消费者查询。

我们利用Redis分布锁+唯一的订单号来查找我们数据库中订单表的订单数据的状态,如果订单状态明确我们就直接幂等返回,反之进行校验逻辑。

其他业务流程类似,分布式锁+唯一订单号进行查询表状态,明确幂等返回。

下游系统提供查询接口

接口超时可能是因为网络传输丢包的问题,也可能是请求时没送到,也可能是请求到了,返回结果丢了。

这时候下游系统提供查询接口来保证幂等。如果接口超时了,先查询对应的记录,如果查到是成功幂等返回,如果失败,按失败流程。

下游系统保证幂等

对于RocketMQ或Redis延迟队列来说会遇到重复消费的场景,需要进行保证幂等。

总结

其实防重和幂等他们的处理流程很类似,核心是需要一个全局的ID来代表唯一性!

我们的项目可以利用以下方法来保证防重和幂等

  • 布隆过滤器

  • 唯一性ID(雪花id)

  • 状态机幂等

  • token令牌

  • Redis分布式锁

  • select+insert+主键/唯一索引冲突

  • 直接insert + 主键/唯一索引冲突

  • 悲观锁(如select for update) 锁住行

  • 数据库悲观锁(select ...for update)解决这个问题这里面order_id需要是索引或主键哈,要锁住这条记录就好,如果不是索引或者主键,会锁表的!悲观锁在同一事务操作过程中,锁住了一行数据。别的请求过来只能等待,如果当前事务耗时比较长,就很影响接口性能。所以一般不建议用悲观锁做这个事情

  • 乐观锁

  • select order_id,version from order where order_id='666';update order set version = version +1,status='P' where order_id='666' and version =1

这篇幂等设计写的很好可以阅读

mp.weixin.qq.com/s/P3GVyHxrS…