专栏首页天马行空布鲁斯空谈分布式系统设计之幂等性

空谈分布式系统设计之幂等性

这篇文章以两个典型的实际案例为基础,聊一聊分布式系统如何实现幂等性。

案例一:转账系统

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

对于一个单体应用版的转账系统,我们可以直接利用数据库的事务来保证整个转账操作的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))。

写在最后的话

通过上面两个案例,我们可以看到:在很多场景,都可以用最终一致性方案替代强一致性来实现分布式事务,这样应用系统可以更加容易地实现高可用(high availability)、可伸缩性(scaling)等特性;但同时也需要非常仔细地从具体业务角度处理幂等性等问题。

本文分享自微信公众号 - 天马行空布鲁斯(gh_2feda5c053bd),作者:huazailmh

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 空谈发件箱模式(outbox pattern)

    基于微服务架构模式(当然不限于)的应用系统,常常会利用消息中间件(kafka,rabbitmq等)来实现各个微服务之间的通信。对于用户的某个操作,一个微服务可能...

    Bruce Li
  • 如何不宕机实现数据库迁移

    由于业务的扩展或者其他原因,常常会有迁移系统数据库的场景,对于有大量用户7*24小时不间断使用的系统,如何不宕机实现数据库迁移,这是个很有挑战的话题。

    Bruce Li
  • 关于分布式系统数据一致性的那些事(二)

    接上一篇文章(关于分布式系统数据一致性的那些事),继续更新一些关于分布式系统数据一致性方面的知识。

    Bruce Li
  • Python数据库操作 Python操作mysql#学习猿地

    mysql数据库可以应用于多种编程语言,包括 PHP,Java,Go,Python

    学习猿地
  • AutoML进入商用化阶段,探智立方全新产品即将上线

    在 2017 年谷歌刚刚推出 AutoML 工具的时候,「AutoML」还仅有机器学习模型自动化的意思。而现在,人们已经普遍认为 AutoML 需要贯穿机器学习...

    机器之心
  • 小白在学习编程的道路上该如何杀出一条血路

    每每面试完之后,心中总会有同样的感慨:大家都是同样的起点,同样的学习周期,同样的老师,为何最终的结果却是鸿沟般的差距?

    用户1272076
  • 揭秘全球最大出行业务背后的数据库系统

    摩拜单车是全球最大智能共享单车运营平台和移动物联网平台,其从成立到全球最大只用了不到3年的时间,拥有全球范围内最为全面的骑行大数据,每天产生的骑行数据超过30...

    腾讯云数据库 TencentDB
  • 揭秘全球最大出行业务背后的数据库系统

    摩拜单车是全球最大智能共享单车运营平台和移动物联网平台,其从成立到全球最大只用了不到3年的时间,拥有全球范围内最为全面的骑行大数据,每天产生的骑行数据超过30...

    腾讯云数据库 TencentDB
  • 从基因组溯源人类进化史

    大数据文摘
  • 恢复没有日志文件的SQL数据库

    由于种种原因,我们如果当时仅仅备份了mdf文件,那么恢复起来就是一件很麻烦的事情了。 如果您的mdf文件是当前数据库产生的,那么很侥幸,也许你使用sp_atta...

    徐大嘴

扫码关注云+社区

领取腾讯云代金券