温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

分布式系统如何实现幂等性

发布时间:2021-10-26 16:26:09 来源:亿速云 阅读:308 作者:iii 栏目:编程语言

这篇文章主要讲解了“分布式系统如何实现幂等性”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“分布式系统如何实现幂等性”吧!

案例一:转账系统

在之前的文章,有多次提到转账系统这个案例,由于这个案例太典型了,很多大学教授数据库事务的时候就是用的这个案例。

对于一个单体应用版的转账系统,我们可以直接利用数据库的事务来保证整个转账操作的ACID。但是,随着用户量级的增加,单个数据库的瓶颈也随之出现,于是就出现了分库分表的设计,即:一部分用户信息存储在一个数据库,另一部分存储在另一个数据库。基于这样的设计,单个数据库的事务肯定就不可用了,我们需要采用跨数据库的分布式事务,比如基于XA协议的分布式事务,但是这种方式有一些自身的问题,并且有应用场景的局限性。所以,一般来说实际场景都是采用基于BASE的最终一致性解决方案。

如下则是一个简单的最终一致性方案设计:

Step 1:Application收到用户发出的一个转账请求之后,首先执行转出方的逻辑,如下:

begin transaction记账单 (包括:转账请求uuid+转账状态in progress)扣钱(转出方余额减少)commit/rollback

这段逻辑包含在一个transaction里面,由于只牵扯到一个数据库,可以利用单个数据库的事务保证。

Step 2:一个background job不断的抓取in progress的记账单,然后发送event(通知收款方收钱)到Kafka,发送成功之后,把账单状态改成success。

这段逻辑就是outbox pattern的实现,关于outbox pattern的具体介绍,可以参考我的另外一篇文章(空谈发件箱模式(outbox pattern))。

Step 3:转入方实现有个listener一直监听这个event,当监听到这个event时,执行如下逻辑:

begin transaction记账单(包括:转账请求uuid+转账状态success)加钱(转入方余额增加)commit/rollback

转入方的逻辑处理也是在一个transaction里面,可以通过单个数据库的事务保证。

但是,上面的设计可能有多个地方会出现event消息重发的情况,比如:background job发送event成功,但是修改账单状态失败;或者,转入方逻辑commit到数据库成功,但是发送ack给Kafka出问题,等等。那么,如何处理这样的重复消费消息的情况呢?因为如果处理不当,就可能会导致数据不一致。其实,这本质上就是一个幂等性问题,保证收到重复消息和收到一次消息的处理结果是一致的,就是幂等的。

对于上面的设计,要保证幂等性,可以在账单表中存一个request uuid,利用这个uuid达到去重的效果,具体是:转入方在收到重复转账event消息时,根据request uuid先去数据库里面检查有没有这个ID存在,有的话则表示这个转账已经处理过了,直接把这个event忽略掉;没有的话则表示需要处理这个event,执行转账。总体来讲,这样的处理逻辑就是幂等的。

当然,实际的转账系统还需要考虑各种错误情况,比如:转入方处理失败的话,可以发送一个反向的event,转出方把之前的扣钱revert回来。

案例二:数据迁移

在之前的文章,也有多次提到数据迁移这个案例。这个案例说的是需要把数据从老的数据库迁移到新的数据库,并且需要保证服务不停止(zero downtime),即不影响用户的正常使用。

对于老数据,可以直接使用一个background job不断的迁移;关键是对于新数据,应该如何“迁移”?一种办法是:双写,即在往老数据库写的同时也往新数据库写,这样来保证新数据在两边都有。

同时往两个数据库写,如何保证两边全成功全失败呢?这又是分布式事务的问题,当时提到了一种方案:best effort 1pc,使用的是Spring提供的ChainedTransactionManager。但是,这种方式在极限情况下也会出现不一致的情况,比如:数据库在特定的时间节点宕机。

下面介绍另外一种基于event方式的双写:在把数据往老数据库写之后,接着把数据本身作为event payload发到Kafka。(这里可以利用outbox pattern来保证at least once delivery)然后,新加一段逻辑,监听这个event,收到这个event之后,把数据写入到新的数据库。

同样的,在监听event这里,需要额外handle下面的情况以保证幂等性:

  1. 收到重复插入数据event(这个情况和上面转账的案例类似)


    对于这种情况,如何实现幂等性处理?

    类似的,可以依赖一个唯一的主键,先根据主键判断数据存不存在。


  2. 消息顺序变化

    消息顺序产生变化,可能的情况有:

    - retry queue,两次连续更新同一条数据的event,第一个event处理失败放进retry queue,而第二个event处理成功。
    - 流量切到新的数据库上时,Kafka里面还有更新数据的event,此时已经有更新数据的请求进来。


    对于这种情况,如何保证幂等性呢?

    关键点是老的event需要被忽略掉。实现层面可以依赖于一个时间戳,不管是迁移数据本身,或者是event对象本身,如果新的event已经处理,则老的event忽略;如果数据已经被更新,则老的event忽略。

上面提到的双写需要再额外增加一个event数据库表,如果可以,也可以采用cdc的方式,这种方式常常用于数据库的复制、备份等场景,利用这种方式,则不需要额外写一张表,而依赖数据库的事务日志,具体可以参考我的另一篇文章(空谈发件箱模式(outbox pattern))。

感谢各位的阅读,以上就是“分布式系统如何实现幂等性”的内容了,经过本文的学习后,相信大家对分布式系统如何实现幂等性这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI