防重是指对请求或消息在一定时间内进行去重,避免产生重复数据。
幂等是保证请求或消息在任意时间内进行处理,需要保证他的结果是一致的。
结合我工作的场景来谈谈防重
如何防止重复下单?
对于我们的系统来说,下单分为三个步骤,是对不同业务系统进行的。
- 第一次是对于我们系统来说的下单,这时候开始生成我们的订单表
- 第二次对于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