Stay hungry, stay foolish.

分布式事务实现方式详解

Categories: Distributed System / Database

1、什么是分布式事务

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。

2、分布式事务的产生的原因

2.1、数据库分库分表

当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务。

2.2、应用SOA化

所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。

以上两种情况表象不同,但是本质相同,都是因为要操作的数据库变多了!

3、分布式事务的应用场景

3.1、支付

最经典的场景就是支付了,一笔支付,是对买家账户进行扣款,同时对卖家账户进行加钱,这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。

3.2、在线下单

买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。

4、常见的分布式事务解决方案

4.1、基于XA协议的两阶段提交

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

XA 协议的工作原理基于 两阶段提交(2PC) 模式。该模式包含以下两个阶段:

  1. 第一阶段:准备阶段(Prepare Phase)
    • 事务管理器(TM)向所有资源管理器(RM)发送一个”准备提交”(prepare)请求,询问它们是否可以提交事务。
    • 每个资源管理器会检查自己的事务状态。如果资源管理器可以安全地提交事务,它会回复一个确认消息(XA_OK);如果资源管理器无法提交事务(如出现数据冲突或错误),它会回复一个回滚请求(XA_RB)。
  2. 第二阶段:提交阶段(Commit Phase)
    • 如果所有的资源管理器在第一阶段中都回复确认,事务管理器会向所有资源管理器发送”提交”(commit)请求,指示它们最终提交事务。
    • 如果任何一个资源管理器在第一阶段回复回滚,事务管理器会向所有资源管理器发送”回滚”(rollback)请求,指示它们撤销之前的操作。

XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。如果在第二阶段(提交或回滚阶段),事务管理器或某个资源管理器发生故障,其他资源管理器可能会被锁住,直到故障恢复,这会导致阻塞。

4.2、 三阶段提交协议(3PC)

三阶段提交协议(3PC,Three-Phase Commit) 是在 2PC 基础上对其进行了改进,旨在解决 2PC 中的阻塞问题,特别是协调者崩溃时可能出现的无法恢复的情况。3PC 协议通过引入”准备提交”阶段来减少阻塞问题。

  • 阶段 1:准备阶段(Can Commit Phase)
    • 协调者向所有参与者询问是否能够准备提交,并确保所有参与者都能够在接收到提交请求时正确执行。
    • 如果所有参与者回复”可以提交”,协调者进入第二阶段。如果有任何参与者无法准备好提交,协调者直接通知回滚。
  • 阶段 2:预提交阶段(Pre-Commit Phase)
    • 如果所有参与者回复”可以提交”,协调者向所有参与者发送预提交请求,告知参与者可以进行准备提交的操作。
    • 参与者收到预提交请求后,会进行所有的操作准备工作,但不正式提交数据。此时,参与者可以进行事务的预提交。
  • 阶段 3:提交阶段(Commit Phase)
    • 如果所有参与者都返回”预提交成功”,协调者发送最终的提交请求,参与者正式提交事务。如果有任何一个参与者无法预提交,协调者会发送回滚请求,事务回滚。

优点:

  • 解决了 2PC 中的阻塞问题,尤其是协调者崩溃时的情况,参与者能够通过时间戳判断当前事务状态。

缺点:

  • 相对于 2PC,3PC 增加了复杂性。引入的预提交阶段虽然可以减少阻塞,但依然存在网络分区问题(即某些参与者无法及时接收到提交指令,依然可能造成一致性问题)

4.3、基于消息的最终一致性

本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:

1、A系统向消息中间件发送一条预备消息 2、消息中间件保存预备消息并返回成功 3、A执行本地事务 4、A发送提交消息给消息中间件

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

步骤一出错,则整个事务失败,不会执行A的本地操作;

步骤二出错,则整个事务失败,不会执行A的本地操作;

步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息;

步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务。

基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。

虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。

4.4、TCC编程模式

所谓的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

Read More

Docker 入门与常用命令

【2024-12-03】Docker 的基础使用方法,包括镜像管理、容器操作、网络配置和 Dockerfile 编写