Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >空谈分布式系统设计之幂等性

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

作者头像
Bruce Li
发布于 2020-10-14 09:47:16
发布于 2020-10-14 09:47:16
96000
代码可运行
举报
运行总次数:0
代码可运行

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

案例一:转账系统

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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时,执行如下逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)等特性;但同时也需要非常仔细地从具体业务角度处理幂等性等问题。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天马行空布鲁斯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
保证分布式系统数据一致性的6种方案
在电商等业务中,系统一般由多个独立的服务组成,如何解决分布式调用时候数据的一致性? 具体业务场景如下,比如一个业务操作,如果同时调用服务 A、B、C,需要满足要么同时成功;要么同时失败。A、B、C 可能是多个不同部门开发、部署在不同服务器上的远程服务。 在分布式系统来说,如果不想牺牲一致性,CAP 理论告诉我们只能放弃可用性,这显然不能接受。为了便于讨论问题,先简单介绍下数据一致性的基础理论。 强一致 当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户
程序员鹏磊
2018/02/09
16.2K0
保证分布式系统数据一致性的6种方案
分布式系统的接口幂等性设计
为了解决以上问题,就需要保证接口的幂等性,接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。有些接口可以天然的实现幂等性,比如查询接口,对于查询来说,你查询一次和两次,对于系统来说,没有任何影响,查出的结果也是一样。
丁D
2022/08/12
2830
关于分布式系统数据一致性的那些事
近些年,随着SOA、微服务架构的流行,分布式系统数据一致性问题也随之而来成为大家热门关注的一个问题。其实,这个问题在很早之前就存在,因为在现实生活中,很多系统都不可能是一个大而全的单机系统,都或多或少需要跟其他系统集成,这种情况就必须需要考虑分布式系统数据一致性。
Bruce Li
2019/08/15
9827
从银行转账失败到分布式事务:总结与思考
  思考这个问题的初衷,是有一次给朋友转账,结果我的钱被扣了,朋友没收到钱。而我之前一直认为银行转账一定是由事务保证强一致性的,于是学习、总结了一下分布式事务的各种理论、方法。
乱敲代码
2019/06/17
9720
从银行转账失败到分布式事务:总结与思考
幂等方案的设计问题
幂等的词典定义:幂等是指同一操作进行多次执行所产生的结果和执行一次的结果是相同的。即无论执行多少次相同的操作,最终的结果都是一致的。幂等在软件行业的定义应当是不论操作执行多少次,其对资源的影响都是一样的。在上图中客户手里的资金是一个资源,预期是不论执行多少次,客户的资金只减少一次。
程序员小志
2025/03/11
1120
幂等方案的设计问题
关于分布式系统数据一致性的那些事(二)
接上一篇文章(关于分布式系统数据一致性的那些事),继续更新一些关于分布式系统数据一致性方面的知识。
Bruce Li
2020/06/15
5640
空谈发件箱模式(outbox pattern)
基于微服务架构模式(当然不限于)的应用系统,常常会利用消息中间件(kafka,rabbitmq等)来实现各个微服务之间的通信。对于用户的某个操作,一个微服务可能需要执行“存数据库”和“发送event”两个步骤。
Bruce Li
2020/10/14
3.3K0
分布式系统的幂等性
幂等性是指多次执行同一操作所产生的结果和效果与执行一次操作的结果和效果相同。换句话说,无论多次执行,结果都是一致的。
一凡sir
2023/11/10
3600
分布式系统的幂等性
分布式服务化系统一致性的“最佳实干”
一致性是一个抽象的、具有多重含义的计算机术语,在不同应用场景下,有不同的定义和含义。在传统的IT时代,一致性通常指强一致性,强一致性通常体现在你中有我、我中有你、浑然一体;而在互联网时代,一致性的含义远远超出了它原有的含义,在我们讨论互联网时代的一致性之前,我们先了解一下互联网时代的特点,互联网时代信息量巨大、需要计算能力巨大,不但对用户响应速度要求快,而且吞吐量指标也要向外扩展(既:水平伸缩),于是单节点的服务器无法满足需求,服务节点开始池化,想想那个经典的故事,一只筷子一折就断,一把筷子怎么都折不断,可见人多力量大的思想是多么的重要,但是人多也不一定能解决所有事情,还得进行有序、合理的分配任务,进行有效的管理,于是互联网时代谈论最多的话题就是拆分,拆分一般分为“水平拆分”和“垂直拆分”(大家不要对应到数据库或者缓存拆分,这里主要表达一种逻辑)。这里,“水平拆分”指的是同一个功能由于单机节点无法满足性能需求,需要扩展成为多节点,多个节点具有一致的功能,组成一个服务池,一个节点服务一部分的请求量,团结起来共同处理大规模高并发的请求量。“垂直拆分”指的是按照功能拆分,秉着“专业的人干专业的事儿”的原则,把一个复杂的功能拆分到多个单一的简单的元功能,不同的元功能组合在一起,和未拆分前完成的功能是一致的,由于每个元功能职责单一、功能简单,让维护和变更都变得更简单、安全,更易于产品版本的迭代,在这样的一个互联网的时代和环境,一致性指分布式服务化系统之间的弱一致性,包括应用系统一致性和数据一致性。
lyb-geek
2022/03/09
6920
分布式服务化系统一致性的“最佳实干”
接口的幂等性
接口的幂等性是指无论调用多少次,接口的执行结果都是一致的。简而言之,对于同一个请求,无论执行一次还是多次,都不会产生不同的结果。这对于系统的可靠性和稳定性至关重要。
GeekLiHua
2025/01/21
770
系统设计——幂等性与解决方案
摘要 幂等概念来自数学,表示N次变换和1次变换的结果是相同的。这里讨论在某些场景下,客户端在调用服务没有达到预期结果时,会进行多次调用,为避免多次重复的调用对服务资源产生副作用,服务提供者会承诺满足幂等。HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的副作用(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
终有救赎
2023/10/16
5320
系统设计——幂等性与解决方案
分布式事务的实现思想
分布式事务的基本概念与本地事务类似,都保证了 ACID 特性(见[本篇第二章](# 二. 事务的特性))。随着数据的规模越来越大,就出现了对业务的解构,包括数据层面的关系型数据库的垂直、水平分表,以及服务层面的拆分,将一个大服务拆分为后单独部署,甚至同时也将数据库独立出来。这时候本地数据库事务就不能满足多个数据库、异构系统的原子性、持久性了,需要使用分布式事务的方法。通常,分布式事务只需要保证原子性,通过保证原子性来保证应用层面的一致性,由本地事务保证隔离性和持久性。 从 CAP 特性上考虑,由于分布式事务存在网络分割的情况,所以一定需要满足分区容忍性,剩下的需要在一致性 (Consistency) 与可用性 (Available) 之间做权衡。下面提到各种分布式事务的实现方法与协议,都是需要在一致性与可用性之间权衡的。
剑影啸清寒
2020/07/13
5600
分布式系统(微服务架构)的一致性和幂等性问题相关概念解析
前言 什么是分布式系统?关于这点其实并没有明确且统一的定义。在我看来,只要一个系统满足以下几点就可以称之为分布式系统 系统由物理上不同分布的多个机器节点组成 系统的多个节点通过网络进行通信,协调彼此之
java架构师
2019/02/26
5680
分布式系统中所说的幂等性
大型网站应用架构中,越来越多的SOA或Restful的web api的流行归功于http协议。 幂等性定义 Http协议涉及到一种重要性质:幂等性。 Http方法的幂等性指一次和多次请求某一个资源应该具有相同的副作用。 分布式事务 vs 幂等设计 先从一个例子说起,假设有一个从账户取钱的远程API(可以是HTTP的,也可以不是),我们暂时用类函数的方式记为: bool withdraw(account_id, amount) withdraw的语义是从account_id对应的账户中扣除amount数额的钱
春哥大魔王
2018/04/17
7580
分布式事务 | 使用DTM 的Saga 模式
前面章节提及的MassTransit、dotnetcore/CAP都提供了分布式事务的处理能力,但也仅局限于Saga和本地消息表模式的实现。那有没有一个独立的分布式事务解决方案,涵盖多种分布式事务处理模式,如Saga、TCC、XA模式等。有,目前业界主要有两种开源方案,其一是阿里开源的Seata,另一个就是DTM。其中Seata仅支持Java、Go和Python语言,因此不在.NET 的选择范围。DTM则通过提供简单易用的HTTP和gRPC接口,屏蔽了语言的无关性,因此支持任何开发语言接入,目前提供了Go、Python、NodeJs、Ruby、Java和C#等语言的SDK。 DTM,全称Distributed Transaction Manager,是一个分布式事务管理器,解决跨数据库、跨服务、跨语言更新数据的一致性问题。它提供了Saga、TCC、 XA和二阶段消息模式以满足不同应用场景的需求,同时其首创的子事务屏障技术可以有效解决幂等、悬挂和空补偿等异常问题。
圣杰
2023/02/10
1.9K0
分布式事务 | 使用DTM 的Saga 模式
分布式系统事务一致性
现今互联网界,分布式系统和微服务架构盛行。业界著名的CAP理论也告诉我们,在设计和实现一个分布式系统时,需要将数据一致性、系统可用性和分区容忍性放在一起考虑。
Java编程指南
2020/06/28
8330
分布式系统事务一致性
一次给女朋友转账引发我对分布式事务的思考
前两天发了工资,第一反应是想着要给远方的女朋友一点惊喜!于是打开了平安银行的APP给女朋友转点钱!填写上对方招商银行卡的卡号、开户名,一键转账!搞定!在我点击的那瞬间,就收到了app的账户变动的提醒,并且出现了图一所示的提示界面:“处理中,正在等待对方银行返回结果…”。嗯!毕竟是跨行转账嘛,等个几秒也正常!脑海开始浮现出女朋友收到转账后惊喜与感动的画面!
冯杰宁
2019/07/01
8550
一次给女朋友转账引发我对分布式事务的思考
消息队列中:消息可靠性、重复消息、消息积压、利用消息实现分布式事务
可以利用消息队列的有序性来验证是否有消息丢失。在Producer端给每个发出的消息附加一个连续递增的序号,然后在Consumer端来检查这个序号的连续性。如果没有消息丢失,Consumer收到消息的序号必然是连续递增的,如果检测到序号不连续,那就是丢消息了。还可以通过缺失的序号来确定丢失的是哪条消息,方便进一步排查原因
搜云库技术团队
2019/11/21
2.2K0
消息队列中:消息可靠性、重复消息、消息积压、利用消息实现分布式事务
分布式系统学习10:分布式事务
单体架构时,以本地事务为例,业务场景是下单场景,用户下单、创建订单、扣减库存这些操作都可以在一个数据库事务中完成。
卷福同学
2025/01/23
1160
分布式系统中的BASE 和 ACID、幂等性、分布式锁、分布式事务与异步消息处理
原子性(A)。所有的系统都受惠于原子性操作。当我们考虑可用性的时候,没有理由去改变分区两侧操作的原子性。而且满足 ACID 定义的、高抽象层次的原子操作,实际上会简化分区恢复。
一个会写诗的程序员
2020/04/30
2K0
分布式系统中的BASE 和 ACID、幂等性、分布式锁、分布式事务与异步消息处理
相关推荐
保证分布式系统数据一致性的6种方案
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验