1.系统为什么需要幂等
- 前端用户的操作问题导致表单重复提交,不做幂等控制会导致创建多个记录,例如用户下单,可能会导致后端创建多个订单
- 用户操作的频繁,导致后端出现高并发问题,同一个订单可能会出现重复支付等问题
- 调用第三方接口,但是系统不做幂等控制,可能会出现两个系统数据不一致问题
- MQ异步消费,不做幂等控制,会出现重复消费问题
2. 解决方案
-
前端防重提交
一般是通过token方式解决,可以参照文章:https://blog.csdn.net/chichuhanga/article/details/79192831 -
后端接口幂等控制
- 接口的流量控制,通过分布式锁来对接口进行流量限制,对于一些可能存在幂等问题的接口,可以限制每个用户每几秒才能调用一次;
- 业务控制,例如创建用户的功能,可以在用户表中设置用户名字段为唯一索引,插入用户记录前先查询一下用户是否存在,存在则返回失败提示;电商中在购物车下单时,为了防止出现创建多个订单的情况,可以在订单表维护一个购物车标识的字段,下单时先校验该字段是否在订单表存在,存在则返回失败等等
- 注意状态机变化,很多业务会存在状态的流转,例如订单会存在未支付,支付中,支付成功,支付失败,退款等等状态,而且进行状态流转时,必须判断前置状态,否则不能更新,根据更新记录数量来判断是否更新失败,从而来做后续的业务操作 update order set status='支付成功' where status='支付中'
- 分布式事务中的幂等控制
分布式事务的解决方案有2PC,TCC,RocketMQ等,现在具体讲解下为什么这里需要分布式事务?
以银行转账为例,用户A银行向B银行转账,A账户扣钱B账户加钱,如果是在同一个数据库,则直接通过数据库的事务保证,扣钱和加钱的逻辑要么都成功要么都失败;但是现实是两个银行,不可能用同一数据库,没法使用数据库的事务保证数据的完全一致性,但是实际上,我们肯定是要保证这两个操作最终的一致性的,就产生了分布式事务的解决方案。
第一种:2PC 理论可以参照文章 https://blog.csdn.net/w372426096/article/details/80449695
还是以银行转账为例,我们引入一个协调者,首先,协调者对A和B账户进行询问并且两个账户分别进行操作,但是不提交事务,我的理解是A账户增加一条冻结记录,并将可用余额扣减,冻结余额增加,如果执行成功返回YES,否则返回NO;B账户也是增加一条冻结流水,可用余额暂时不会增加,如果执行成功返回YES,否则返回NO;第二个阶段,协调者根据两个返回的值来判断下一步操作:如果都返回YES,则发送通知,让A的冻结余额扣除;B的可用余额增加;如果收到NO,则进行回滚,A的可用余额加回来,B的冻结记录删除。
第二种:3PC
在2PC的基础上,将第一个阶段一分为二,canCommit和preCommit,具体理论可以百度,3PC和2PC最大的区别是增加了超时自动提交的机制,当参与者处于preCommit或者commit时,如果由于网络或者协调者宕机等问题,导致参与者阻塞超时时,参与者也会自动提交,避免了2PC由于网络等问题导致的参与者长时间阻塞问题,分析了下原因,因为系统认为处于preCommit状态的参与者,极大概率是可以进行commit的,但是不能最终解决这种问题,因为
处于preCommit状态不一定最终就可以进行commit,可能某个参与者在preCommit执行失败,可能会导致最终的数据不一致性。
第三种:TCC 补偿型
还是以A银行账户向B银行账户转账为例,调用转账接口之前,先冻结A金额,并且插入转账记录,一般银行接口会提供幂等字段做查询用,然后调用银行转账接口;使用定时任务或者一部任务等拿这个幂等的字段去B银行查询,如果成功,则A金额解冻。TCC会有很多补偿性代码,当业务较复杂时,补偿性代码会很多。还有用MQ实现分布式事务的,RocketMQ支持分布式事务,它能保证A银行的插入转账记录和发送消息给B银行加钱这两个逻辑肯定会同时成功或者失败,但是我觉得最后可能还是会结合补偿代码进行结合来保障最终的一致性。
- MQ重复消费
1.通过消息id来做处理
消息重复消费时,Message Id时一样的,我们在消费前可以先判断该消息有没有被消费过,消费过了就不做处理;但是message id不能确保唯一性,一般不建议使用此方法
2.通过业务键保证幂等
一般消息处理中会带有一个业务幂等键,通过此字段来做控制,此键一般作为主键,先从表中根据该字段查询出有没有记录,如果没有进行插入。
网友评论