分布式-分布式事务理论、模型、方案、框架
一、分布式事务理论模型
分布式事务问题也叫分布式数据一致性问题,简单来说就是如何在分布式场景中保证多个节点数据的一致性。分布式事务产生的核心原因在于存储资源的分布性,比如多个数据库,或者MySQL和Redis两种不同存储设备的数据一致性等。在实际应用中,我们应该尽可能地从设计层面去避免分布式事务的问题,因为任何一种解决方案都会增加系统的复杂度。接下来我们了解一下分布式事务问题的常见解决方案。
1.1 X/Open分布式事务模型
X/Open DTP(X/Open Distributed Transaction Processing Reference Model)是X/Open这个组织义的一套分布式事务的标准这个标准提出了使用两阶段提交(2PC,Two-Phase-Commit)来保证分布式事的完整性。如图8-2所示,X/Open DTP中包含以下三种角色。
- AP:Application,表示应用程序
- RM:Resource Manager,表示资源管理器,比如数据库
- TM:Transaction Manager,表示事务管理器,一般指事务协调者,负责协调和管理事务,提供AP编程接口或管理RM。可以理解为Spring中提供的Transaction Manager。
图8-2所展示的角色和关系与本地事务的原理基本相同,唯一不同的在于,如果此时RM代表数据库,那么TM需要能够管理多个数据库的事务,大致实现步骤如下:
-
配置TM,把多个RM注册到TM,相当于TM注册RM作为数据源
-
AP从TM管理的RM中获取连接,如果RM是数据库则获取JDBC连接
-
AP向TM发起一个全局事务,生成全局事务ID(XID),XID会通知各个RM
-
AP通过第二步获得的连接直接操作RM完成数据操作。这时,AP在每次操作时会把XID传递给RM。
-
AP结束全局事务,TM会通知各个RM全局事务结束
-
根据各个RM的事务执行结果,执行提交或者回滚操作。
实际上这里会涉及全局事务的概念。也就是说,在原本的单机事务下,会存在跨库事务的可见性问题,导致无法实现多节点事务的全局可控。而TM就是一个全局事务管理器,它可以管理多个资源管理器的事务。TM最终会根据各个分支事务的执行结果进行提交或者回滚,如果注册的所有分支事务中任何一个节点事务执行失败,为了保证数据的一致性,TM会触发各个RM的事务回滚操作。
需要注意的是,TM和多个RM之间的事务控制,是基于XA协议(XA Specification)来完成的。XA协议是X/Open提出的分布式事务处理规范,也是分布式事务处理的工业标准,它定义了xa和ax系列的函数原型及功能描述、约束等。目前Oracle、MySQL、DB2都实现了XA接口,所以它们都可以作为RM。
二、分布式一致性协议
2.1 两阶段提交协议:准备阶段、提交阶段
2.1.1致命的问题
- 阻塞:准备阶段会锁定资源,直到下一步完成。
- 单点故障:如果协调者(事务管理器)宕机,会一直阻塞。
- 脑裂:协调者发送提交命令,有些参与者没有收到,不会执行事务。多个参与者之间不一致。
2.2.2 两阶段提交协议的流程
- 准备阶段:协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则写redo或undo日志(Write-Ahead Log的一种),然后锁定资源,执行操作但不提交
- 提交阶段:如果每个参与者都明确返回准备成功,则协调者向参与者发起提交指令,参与这提价资源变更的事务,释放锁定的资源.如果任何一个参与者明确返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者明确返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源.
致命问题
- 阻塞:任何指令都要收到明确的响应,否则处于阻塞状态占用资源被一直锁定,不会被释放
- 单点故障:协调者宕机,会一直阻塞,选举新的协调者替代原有协调者,但如果协调者发送一个提交指令后宕机,而提交指令仅仅被一个参与者接收,并且参与者接收后也宕机,新上任的协调者无法处理这种情况.
- 脑裂:协调者发送提交指令,有的参与者执行,有的参与者没有接收到事务就没执行,多个参与者之间不一致.
事务协调者的单点故障:如果协调者在第二阶段出现了故障,那么其他的参与者(RM)会一直处于锁定状态。
脑裂:导致数据不一致问题:在第二阶段中,事务协调者向所有参与者(RM)发送commit请求后,发生局部网络异常导致只有一部分参与者(RM)接收到了commit请求,这部分参与者(RM)收到请求后会执行commit操作,但是未收到commit请求的节点由于事务无法提交,导致数据出现不一致问题
2.2 三阶段提交协议:询问阶段、准备阶段、提交阶段
三阶段提交协议是两阶段提交协议的改进版本.它通过超时机制解决了阻塞的问题,把两个阶段增加为三个
- 询问阶段:参与者只要回答是或不是,不需要真正操作,这个阶段超时会导致中止
- 准备阶段:如果在寻味阶段所有参与者都返回可以执行操作,则协调者向参与者发送预执行请求,参与者写redo和undo日志,执行操作但不提交,这里逻辑和两阶段提交的准备阶段相似
- 提交阶段:如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的操作,释放锁定的资源;如果任意参返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者发送终止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源,这里的逻辑与两段提交协议的;
2.2.1 三阶段提交协议和两阶段提交协议相比有一些不同点
增加了一个CanCommit阶段,用于询问所有参与者是否可以执行事务操作并且响应,它的好处是,可以尽早发现无法执行操作而中止后续的行为。
在准备阶段之后,事务协调者和参与者都引入了超时机制,一旦超时,事务协调者和参与者会继续提交事务,并且认为处于成功状态,因为在这种情况下事务默认为成功的可能性比较大。
实际上,一旦超时,在三阶段提交协议下仍然可能出现数据不一致的情况,当然概率是比较小的。另外,最大的好处就是基于超时机制来避免资源的永久锁定。
*需要注意的是,不管是两阶段提交协议还是三阶段提交协议,都是数据一致性解决方案的实现,我们可以在实际应用中灵活调整。比如ZooKeeper集群中的数据一致性,就用到了优化版的两阶段提交协议,优化的地方在于,它不需要所有参与者在第一阶段返回成功才能提交事务,而是利用少数服从多数的投票机制来完成数据的提交或者回滚。
可以看出,从时序上来说,如果遇到极端情况,则 TCC 会有很多问题,例如,如果在取消时一些参与者收到指令,而另 些参与者没有收到指令,则整个系统仍然是不 致的 。对于这种复杂的情况,系统首先会通过补偿的方式尝试自动修复,如果系统无法修复,则必须由人参与解决TCC 的逻辑上看,可以说 TCC 是简化版的 阶段提交协议,解决了两阶段提交协议的阻塞问题,但是没有解决极端情况下会出现不一致和脑裂的问题 然而, TCC 通过自动化补手段 ,将需要人工处理的不 致情况降到最少,也是 种非常有用的解决方案。某著名 互联网公司在内部的 些中间件上实现了 TCC 模式。我们给出 个使用 TCC 的实际案例,在秒杀的场景中,用户发起下订单请求,应用层先询库存,确认商品库存还有余量,则锁定库存,此时订单状态为待支付,然后指引用户去支付由于某种原因用户支付失败或者支付超时,则系统会自动将锁定的库存解锁以供其他用户秒杀。
三、 分布式一致性理论
前面提到的两阶段提交和三阶段提交是XA协议解决分布式数据一致性问题的基本原理,但是这两种方案为了保证数据的强一致性,降低了可用性。实际上这里涉及分布式事务的两个理论模型。
3.1数据库一致性理论基础
ACID(酸)
- A: Atomicity,原子性
- C: Consistency,一致性
- I: Isolation,隔离性
- D: Durability,持久性
关系型数据库天生用于解决具有复杂事务场景的问题,完全满足ACID的特性。
具有ACID特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致,每个事务都是原子的,或者成功或者失败,事务间是隔离的,互相完全不受影响,而且最终状态是持久落盘的。
NoSQL完全不适合交易场景,主要用来做数据分析、ETL、报表、数据挖掘、推荐、日志处理、调用链跟踪等非核心交易场景
?现在我们来看看下订单和扣库存一致性问题,如果是在数据量较小的情况下,可以利用关系型数据库的强一致性解决,也就是把订单表和库存表放在同一个关系型数据库中,利用关系型数据库进行下订单和扣库存两个紧密相关的操作,达到订单和库存实时一致的结果。如果是大规模高并发的情况,由于业务规则的限制,无法将相关数据分到同一个数据库分片,这时就需要实现最终一致性。
3.2 CAP定理 (帽子原理)
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统。
- C: Consistency,一致性。在分布式系统中的所有数据备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本。
- A: Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应。
- P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。
CAP原理证明,任何分布式系统只可同时满足以上两点,无法三者兼顾。由于关系型数据库是单节点无复制的,因此不具有分区容忍性,但是具有一致性和可用性,而分布式的服务化系统都需要满足分区容忍性,那么我们必须在一致性和可用性之间进行权衡。
所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。
redis 就是 AP 模型 ,zk 就是 CP
一般来说基于CP 一般来说具有性能瓶颈问题;
基于CAP理论我们知道对于数据一致性问题有AP和CP两种方案,但是在电商领域等互联网场景下,基于CP的强一致性方案在数据库性能和系统处理能力上会存在一定的瓶颈。所以在互联网场景中更多采用柔性事务,所谓的柔性事务是遵循BASE理论来实现的事务模型,它有两个特性:基本可用、柔性状态。在本节中主要基于柔性事务模型来分析互联网产品中分布式事务的常见解决方案。
3.3 BASE(碱)
BASE思想解决了CAP提出的分布式系统的一致性和可用性不可兼得的问题。BASE思想与ACID原理截然不同,它满足CAP原理,通过牺牲强一致性获得可用性,一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。
BASE平衡理论,简单来说就是在不同的场景下,可以分别利用ACID和BASE来解决分布式服务化系统的一致性问题。
- BA: Basically Available,基本可用
- S: Soft State,软状态,状态可以在一段时间内不同步
- E: Eventually Consistent,最终一致,在一定的时间窗口内,最终数据达成一致即可
软状态是实现BASE思想的方法,基本可用和最终一致是目标。以BASE思想实现的系统由于不保证强一致性,所有系统在处理请求的过程中存在短暂的不一致,在短暂的不一致的时间窗口内,请求处理处于临时状态中,系统在进行每步操作时,通过记录每个临时状态,在系统出现故障时可以从这些中间状态继续处理未完成的请求或者退回到原始状态,最终达到一致状态。
BASE对酸碱平衡的总结
| 解决一致性问题的三条实践经验:使用向上扩展(强悍的硬件)并运行专业的关系型数据库,能够保证强一致性;关系型数据库水平伸缩和分片,将相关数据分到数据库的同一个片上;最终一致性。
四、保证最终一致性的模式
4.0 两端协议、TCC协议缺点
在大规模、 高并发服务化系统中,一 个功能被拆分成多个具有单 功能的子功能, 一个流程会有多个系统的多个单 功能的服务组合实现,如果使用两阶段提交协议和 阶段提交协议,能解决系统间的 致性问题 除了这两个协议的自身问题,其实现也比较复杂、成本比较高,最要的是性能不好,相比来看, TCC协议更简单且更容易实现,但是 TCC协议由于个事务都需要执行 Try ,再执行 Confirm 略显雕肿,因此,现实系统的底线是仅仅 要达到最终一致性,而不需要实现专业 业的、复杂的一致 致性协议。
实现最终一致性有些非常有效、简单式的模式,下面就介绍这些模式及其应用场景实现最终一致性非常有效、简单的模式。
- 查询模式:每个服务操作都需要有唯一的流水号标识,用方可以通过查询接口得知服务操作执行的状态,然后根据不同的状态来做不同的处理操作。
补偿模式:自动恢复、通知运营、技术运营。 - 异步确认模式:将要执行的异步操作封装后持久入库,然后通过定时捞取未完成的任务进行补偿操作来实现异步确保模式。
- 定期校对模式:需要全局唯一ID
- 可靠消息模式
- 缓存一致性模式
4.1查询模式
每个服务操作都需要有唯一的流水号标识,用方可以通过查询接口得知服务操作执行的状态,然后根据不同的状态来做不同的处理操作。?为了能够实现查询,每个服务操作都需要有唯 的流水号标识,也可使用此次服务操作对应的资源 ID 来标识,例如:请求流水号、订单号等首先,单笔查询操作是必须提供的,也鼓励使用单笔订单查询,这是因为每次调用 需要占用的负载是可控的。批量查询则根据 要来提供,如果使用了批 查询,则 要有合理的分页机制,并且必须限制分页的大小,以及对批量查询的吞吐量有容 评估、熔断、隔离和限流等措施;
4.2 补偿模式
有了上面的查询模式,在任何情况下,我们都能得知具体的操作所处的状态,如果整个操作都处于不正常的状态,则我们需要修正操作中有问题的子操作,这可能需要重新执行未完成的子操作,后者取消己经完成的子操作,通过修复使整个分布式系统达到 为了让系统终达到 致状态而做的努力都叫作补偿。
若业务操作发起方还没有收到业务操作执行方的 明确返回或者调用超时,则用查询模式查询业务执行方的执行状态,在获取业务执行方的状态后,如果业务员执行方已经执行成功,则业务发起方回调,告诉业务发起方执行成功;如果业务发起方的执行状态为失败或者未知,也叫作快速失败策略,然后执行业务操作的逆向操作,保证不被执行或者回滚已经执行的操作,让业务发起方、业务操作方的状态最终一致;
4.2.1补偿操作根据发起形式分为以下几种。
- ?自动恢复:程序根据发生不一致的环境,通过继续进行未完成的操作,或者回滚己经完成的操作,来自动达到 致状态。
- 通知运营:如果程序无法自动恢复,并且设计时考虑到了不一致的场景,则可以提供运营功能,通过运营手工进行补偿。
- 技术运营:如果很不巧,系统无法自动回复,又没有运营功能,那么必须通过技术手段来解决,技术手段包括进行数据库变更或者代码变更,这是最糟的 种场景,也是我
们在生 中尽 避免的场景;
4.3 异步确保模式
异步确保模式是补偿模式的 个典型案例,经常应用到使用方对响应时间要求不太高的场景中,通常把这类操作从主流程中摘除,通过异步的方式进行处理,处理后把结果通过通知系统通知给使用方 。这个方案的最大好处是能够对高并发流量进行消峰,例如:电商系统中的物流、配迭,以及支付系统中的计费、入账等。
在实践中将要执行的异步操作封装后持久入库,然后通过定时捞取未完成的任务进行补偿操作来实现异步确保模式,只要定时系统足够健壮,则任何任务最终都会被成功执行
若对某个操作迟迟没有收到响应,则通过查询模式、补偿模式和异步确保模式来继续未完成的操作。
4.4 定期校对模式
系统在没有达到 致之前,系统间的状态是不 致的,甚至是混乱的, 要通过补偿操作来达到最终 致性的目的,但是如何来发现需要补偿的操作呢?
在操作主流程中的系统间执行校对操作,可以在事后异步地批量校对操作的状态,如果发现不致的操作,则进行补偿,补偿操作与补偿模式中的补偿操作是一致的。另外,实现定期校对的 个关键就是分布式系统中需要有 个自始 终唯 ID局唯一ID 有以下两种方法(分布式ID)。
- 持久型: 使用数据库表自增字段或者 Sequence 生成,为了提高效率,每个应用节点可以缓存一个批次的 ID ,如果机器重启则可能会损失 部分 ID ,但是这并不会产问题。
- 时间型:一般由机器号、业务号、时间、单节点内自增 组成,由于时间 般精确秒或者毫秒,因此不需要持久就能保证在分布式系统中全局唯 、粗略递增等;
在实践中想在分布式系统中迅速定位问题时,可通过分布式系统的调用链跟踪系统进行,它能够跟踪 个请求的调用链。调用链是从二维的维度跟踪一个调用请求 最后形成一个调用树,其原理可参考谷歌 Dapper 论文及它的 个流行的开源实现项目 Pinpoint;
4.5 可靠消息模式
在分布式系统中,对于主流程中优先级 比较低的操作,大多采用异步的方式执行,也就是前面提到的异步确保模型,为了让异步操作的调用方和被调用方充分理解,也由于专业的消息队列本身具有可伸缩、可分片、可持久等功能,我们通常通过消息队列实现异步化。对于消息队列,我们需要建立特殊的设施来保证可靠的消息发送及 处理机的幕等性。
4.5.1 消息的可靠发送
消息的可靠发送可以认为是尽最大努力发送消息通知,有以下两种实现方法。
- 第一种,在发送消息之前将消息持久到数据库,状态标记为待发送 然后发送消息,如果发送成功,则将消息改为发送成功。定时任务定时从数据库捞取在一定时间内未发送的消息并将消息发送。可靠消息发送模式 1 ,如图2-10 所示。
- 第二种,该实现方式与第一种类似,不同的是持久消息的数据库是独立的, 并不藕合在业务系统中。发送消息前,先发送 个预消息给某个第三方的消息管理器,消息管理器将其持到数据库,并标记状态为待发送,在发送成功后,标记消息为发送成功。定时任务定时从数据库中捞取一定时间内未发送的消息,查询业务系统是否要继续发送,根据查询结果来确定消息的状态。可靠消息发送模式 如图 2-11 所示。
4.5.2 消息处理器的幂等性处理
如果我们要保证可靠地发送消息,简单来说就是要保证消息一定发送出去,那么需要有重试机制。有了重试机制后,消息就一定会重复,那么我们需要对重复的问题进行处理。处理重复问题的最佳方式是保证操作的幂等性;
保证操作的幂等性的常用方法如下。
1)使用数据库表的唯一键进行滤重,拒绝重复的请求。
2)使用分布式表对请求进行滤重。
3)使用状态流转的方向性来滤重,通常使用数据库的行级锁来实现。
4)根据业务的特点,操作本身就是幕等的 例如 删除 个资源、增加一个资源、获得个资源等。
4.6 缓存一致性模式
在大规模、高并发系统中的一个常见的核心需求就是亿级的读需求,显然,关系型数据库并不是解决高并发读需求的最佳方案,互联网 的经典做法就是使用缓存来抗住读流量。下面是使用缓存来保证一致性的最佳实践。
1) 如果性能要求不是非常高,则尽量使用分布式缓存,而不要使用本地缓存。
2) 写缓存时数据一定要完整, 如果缓存数据的一部分有效 另一部分无效,则宁可在需要时回源数据库,也不要把部分数据放入缓存中。
3)使用缓存牺牲了一 致性,为了提高性能,数据库与缓存只需要保持弱一致性,而不需要保持强 致性,否则违背了使用缓存的初衷。
4) 读的顺序是先读缓存,后读数据库,写的顺序要先写数据库,后写缓存。
五、超时处理模式?
微服务的交互模式:同步调用模式、接口异步调用模式、消息队列异步处理模式
- 同步与异步的抉择
- 交互模式下超时问题的解决方案
- 超时补偿的原则
5.1 接口调用模式
5.1.1 同步调用模式
在同步调用模式中,服务 1调用服务2 ,服务1 的线程阻塞等待服务2 返回处理结果,如果服务 2一直不返回处理结果, 则服务2一 直等待到超时为止;
同步调用模式适用于大规模、高并发的短小操作,而不适用于后端负载较高的场景,例如:几乎所有 JDBC 的实现完全使用 BIO 同步阻塞模式
5.1.2 接口异步调用模式
在接口异步调用模式中 ,服务1 请求服务2接 受理某项任务,服务2 受理后即刻返回给服1其受理结果 ,如果受理成功,则服务1继续做其他任务,而服务异2步地处理这项任务,直到服务2处理完这项任务后,才反向地通知服务1 任务已经完成,服务1再做后续处理;
接口异步调用模式适用于非核心链路上负载较高的处理环节,这个环节经常耗时较长,并且对时效性要求不高。 例如:在 B2C 电商系统中,一件商品售卖成功后,需要给相应的商户入账收入,这个过程对时效性要求不高,可以使用接口异步调用模式。
5.1.3 消息队到异步处理模式
**消息队列异步处理模式利用消息队列作为通信机制 ,在这种交互模式中 ,通常服1 只需将某种事件传递给服务2 ,而不需要等待服务2返回结果。**在这样的场景下,服务1 与服务2可以充分理解 ,并且在大规模、高并发的微服务系统中,消息队列对流量具有消峰的功能。
?消息队列异步处理模式与接口异步调用模式类 ,多应用于非核心 链路上负载较高的处理环节中,井且服务的上游不关心下游的处理结果,下游也不需要向上游返回处理结果 例如:
在电商系统中,用户下订单支付且交易成功后,后续的物流处理适合使用消息队列异步处理模式,因为物流发货属于物流和配送系统的职责,不应该影响交易,所以交易系统不需要对其有感知;
以上 种交互模式普遍应用于服务化和微服务架构中 ,它们之间没有绝对的好坏,只需要在特定场景下做出更适合的选择。
5.1.4 同步与异步的抉择
一些互联网公司试图通过规范来约束这三种方式的使用和选择,下面是笔者在工作中收集的两个不同的团队倡导的关于同步和异步选择的原则。
- 1.尽量使用异步来替换同步操作。
- 2.能用同步解决的问题,不要引入异步。
这两个原则从字面意义上看是完全不同的,甚至是矛盾的。实际上 这里的原则都没有错,只不过原则抽象得太干净利落,以至于没有给出适合这些原则的环境信息;
-
?第一条原则是从业务功能的角度出发的,也就是从与用户或者使用方的交互模式出发的,如果业务逻辑允许,用户对产品的交互形态没有异议,则我们可以将一些耗时较长的、用户对响应没有特别要求的操作异步化,以此来减少核心链路的层级,释放系统的压力 。例如 12306在订票高峰期会开启订票异步模式 在购票后用户并不会马上得知购票的结果,而是后续通过查询得知结果,这样系统便赢得了为成千上万的用户处理购票逻辑的时间。
-
?第二条原则是从技术和架构的角度出发的,这条原则应用的前提是同步能够解决问题,隐含了 个含义:如果性能不是问题,或者所处理的操作是短小的轻量级处理逻辑,那么同步调用方式是最理想不过的,因为这样不需要引入异步化的复杂处理流程。例如 :所有 JDBC实现使用同步阻塞的 BIO 模型,即访问数据库操作时无论是查询还是更新,原则上都是短小操作,不需要异步化,而是在同步过程中完成请求的受理和处理过程,这也是为什么不推荐将大数据存储到关系型数据库中,关系型数据库只存储交易相关的最小化核心信息。
5.2 交互模式下超时问题的解决方案
5.3 超时补偿的原则
对于 2.4.3 节的多个场景来说,
我们都需要对服务间同步超时造成的后果进行处理,而处理方法有快速失败和内部补偿两种,补偿模式也有调用方补偿和接收方补偿两种,
具体使用哪种方式呢?
通过这个案例,我们很容易理解服务间调用超时补偿的原则服务 1调用服务2 ,如果服务2 响应服务1并且告诉服务 消息己接收,那么服务1任务就结束了;如果服务2 处理失败,那么服务2 应该负责重试或者补偿。在这种情况下,服务 2通常接收消息后先持久再告诉服务1 接收成功,随后服务2 才开始处理持久的消息,避免服务进程被杀掉而导致消息丢失,;
?服务1 调用服务2 ,如果服务2 没有给出明确的接收响应,例如网络超时,那么服务1应该持续进行重试,直到服务2 明确表示己经接收消息 在这种情况下容易出现重复
的消息,因此在服务 2中通常要保证滤重或者幕等性。
六、 分布式事务问题的常见解决方案
在前面的章节中已经详细分析了分布式事务的问题及理论模型,并且基于CAP理论我们知道对于数据一致性问题有AP和CP两种方案,但是在电商领域等与联网场景下,基于CP的强一致性方案在数据库性能和系统处理能力上会存在一定的瓶颈。所以在互联网场景中更多采用柔性事务,所谓的柔性事务是遵循BAS理论来实现的事务模型,它有两个特性:基本可用、柔性状态。在本节中主要基于柔性事务模型来分析互联网产品中分布式事务的常见解决方案。
6.1TCC 补偿型方案
TCC(Try-Confirm-Cancel)是一种比较成熟的分布式数据一致性解决方案它实际上是把一个完整的业务拆分为如下三个步骤。
- Try:这个阶段主要是对数据的校验或者资源的预留
- Confirm:确认真正执行的任务,只操作Try阶段预留的资源
- Cancel:取消执行,释放Try阶段预留的资源
其实TCC是一种两阶段提交的思想,第一阶段通过Ty进行准备工作,第二阶段Confirm/Cancel表示Try阶段操作的确认和回滚。在分布式事务场景中,每个服务实现TCC之后,就作为其中的一个资源,参与到整个分布式事务中。然后主业务服务在第一阶段中分别调用所有TCC服务的Try方法。最后根据第一个阶段的执行情况来决定对第二阶段的Confirm或者Cancel
对于TCC的工作机制,我们举一个比较简单的例子。在一个理财App中,用户通过账户余额购买一个理财产品,这里涉及两个事务操作:
- 在账户服务中,对用户账户余额进行扣减
- 在理财产品服务中,对指定理财产品可申购金额进行扣减。
这两个事务操作在微服务架构下分别对应的是两个不同的微服务,以及独立的数据库操作,在TCC的工作机制中,首先针对账户服务和理财产品服务分别提供Try、Confirm和Cancel三个方法。
- 在账户服务的Try方法中对实际申购金额进行冻结,Confirm方法把Try方法冻结的资金进行实际的扣减,Cancel方法把Try方法冻结的资金进行解冻。
- 理财产品服务的Try方法中将本次申购的部分额度进行冻结,Confirm方法把Try方法中冻结的额度进行实际扣减,Cancel方法把Ty方法中冻结的额度进行释放。
在一个主业务方法中,分别调用这两个服务对外提供的处理方法(资金扣减、理财产品可申购额度扣减)这两个服务做实际业务处理时,会先调用Trv方法来做资源预留,如果这两个方法处理都正常,TCC事务协调器就会调用Confirm方法对预留资源进行实际应用。否则TCC事物协调器一旦感知到任何一个服务的Try方法处理失败,就会调用各个服务的Cancel方法进行回滚,从而保证数据的一致性。
在一些特殊情况下,比如理财产品服务宕机或者出现异常,导致该服务并没有收到TCC事务协调器的Cance或者Confirm请求,怎么办呢?没关系,TCC事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态。TCC事务协调器会根据操作日志来进行重试,以达到数据的最终一致性。
需要注意的是,TCC服务支持接口调用失败发起重试,所以TCC暴露的接口都需要满足幂等性;
6.2 基于可靠性消息的最终一致性方案
基于可靠性消息的最终一致性是互联网公司比较常用的分布式数据一致性解决方案,它主要利用消息中间件(KafkaRocketMQ或RabbitMQ)的可靠性机制来实现数据一致性的投递。
以电商平台的支付场景为例,用户完成订单的支付后不需要同步等待支付结果,可以继续做其他事情。但是对于系统来说,大部分是在发起支付之后,等到第三方支付平台提供异步支付结果通知,再根据结果来设置该订单的支付状态。并目如果是支付成功的状态,大部分电商平台基于营销策略还会给账户增加一定的积分奖励。所以,当系统接收到第三方返回的支付结果时,需要更新支付服务的支付状态,以及更新账户服务的积分余额,这里就涉及两个服务的数据-致性问题。
从这个场景中可以发现这里的数据一致性并不要求实时性,所以我们可以采用基于可靠性消息的最终一致性方案来保证支付服务和账户服务的数据一致性。如图8-7所示,支付服务收到支付结果通知后,先更新支付订单的状态,再发送一条消息到分布式消息队列中,账户服务会监听到指定队列的消息并进行相应的处理,完成数据的同步。
在图8-7的解决方案中,我们不难发现一些问题,就是支付服务的本地事务与发送消息这个操作的原子性问题,具体描述如下;
以上问题也有很多成熟的解决方案,以RocketMQ为例,它提供了事务消息模型,如图8-8所示,具体的执行逻辑如下:
- 生产者发送一个事务消息到消息队列上,消息队列只记录这条消息的数据,此时消费者无法消费这条消息。
- 生产者执行具体的业务逻辑,完成本地事务的操作
- 接着生产者根据本地事务的执行结果发送一条确认消息给消息队列服务器,如果本地事务执行成功,则发送一个Commit消息,表示在第一步中发送的消息可以被消费,否则,消息队列服务器会把第一步存储的消息删除。
- 如果生产者在执行本地事务的过程中因为某些情况一直未给消息队列服务器发送确认,那么消息队列服务器会定时主动回查生产者获取本地事务的执行结果,然后根据回查结果来决定这条消息是否需要投递给消费者。
- 消息队列服务器上存储的消息被生产者确认之后,消费者就可以消费这条消息,消息消费完成之后发送一个确认标识给消息队列服务器,表示该消息投递成功。
在RocketMQ事务消息模型中,事务是由生产者来完成的,消费者不需要考虑,因为消息队列可靠性投递机制的存在,如果消费者没有签收该消息,那么消息队列服务器会重复投递,从而实现生产者的本地数据和消费者的本地数据在消息队列的机制下达到最终一致。
不难发现,在RocketMQ的事务消息模型中最核心的机制应该是事务回查,实际上查询模式在很多类似的场景中都可以应用。在分布式系统中,由于网络通信的存在,服务之间的远程通信除成功和失败两种结果外,还存在一种未知状态,比如网络超时。服务提供者可以提供一个查询接口向外部输出操作的执行状态,服务调用方可以通过调用该接口得知之前操作的结果并进行相应的处理。
6.3 最大努力通知型
最大努力通知型和基于可靠性消息的最终一致性方案的实现是类似的,它是一种比较简单的柔性事务解决方案也比较适用于对数据一致性要求不高的场景,最典型的使用场景是支付宝支付结果通知,实现流程如图8-9 所示;
七、分布式事务框架Seata
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。它提供了AT、TCC、Saga和XA事务模式,为开发者提供了一站式的分布式事务解决方案。其中TCC和XA我们前面分析过,AT和Saga这两种事务模式是什么呢?下面先来简单介绍一下这两种事务模式;
7.1 AT模式
AT模式是Seata最主推的分布式事务解决方案,它是基于XA演进而来的一种分布式事务模式,所以它同样分为三大模块,分别是TM、RM和TC,其中TM和RM作为Seata的客户端与业务系统集成,TC作为Seata的服务器独立部署。TM表示事务管理器(Transaction Manager),它负责向TC注册一个全局事务,并生成一个全局唯一的XID。在AT模式下,每个数据库资源被当作一个RM(Resource Manager),在业务层面通过JDBC标准的接口访问RM时,Seata会对所有请求进行拦截。每个本地事务进行提交时,RM都会向TC(TransactionCoordinator,事务协调器)注册一个分支事务。Seata的AT事务模式如图8-10所示。
具体执行流程如下:
- TM向TC注册全局事务,并生成全局唯一的XID。
- RM向TC注册分支事务,并将其纳入该XID对应的全局事务范围
- RM向TC汇报资源的准备状态
- TC汇总所有事务参与者的执行状态,决定分布式事务是全部回滚还是提交
- TC通知所有RM提交/回滚事务
AT模式和XA一样,也是一个两阶提交事务模型,不过和XA相比,做了很多优化,笔者会在后续的章节中重点分析AT模式的实现原理
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!