2、分布式事务基础:本地事务与分布式事务、ACID特性在分布式环境下的挑战、XA协议与2PC模型
好,咱们今天聊聊分布式事务的底子。说实话,很多同学一上来就追各种分布式事务框架,结果遇到问题就懵了。我个人习惯是,先把地基打牢——本地事务、ACID、XA协议、2PC,这些搞明白了,后面看Seata、TCC那些方案,你一眼就能看穿它们的本质。
2.1 本地事务:你每天都在用的老朋友
先说说本地事务。说白了,就是在一个数据库连接里完成的事务。比如你在MySQL里执行一个转账操作:
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
UPDATE account SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
这就是最经典的本地事务。它依赖数据库自身的日志和锁机制来保证数据一致性。我在项目中遇到过不少新人,觉得本地事务太简单,结果在并发场景下踩了坑——比如忘了设置合适的隔离级别,导致脏读、幻读。
2.2 分布式事务:当数据散落在多个节点
那分布式事务呢?就是一次操作涉及多个数据库、多个服务。比如下单场景:订单库扣库存、支付库扣钱、积分库加积分。这三个操作必须要么全成功,要么全失败。
为什么会这么麻烦?因为每个数据库都有自己的事务管理器,它们之间没法直接通信。你没法让MySQL和Redis说:「嘿,咱俩一起提交吧。」
我记得有一次,一个同事在微服务里直接用了本地事务,结果订单创建成功了,库存却扣失败了。用户付了钱,但商品没货——这就是典型的分布式事务问题。
2.3 ACID特性在分布式环境下的挑战
ACID这四个特性,在单机数据库里很完美。但到了分布式环境,每个特性都面临挑战。我一个个说。
| ACID特性 | 单机环境 | 分布式环境挑战 |
|---|---|---|
| 原子性(Atomicity) | 数据库通过undo log保证 | 多个节点间无法共享undo log,需要协调者 |
| 一致性(Consistency) | 约束由数据库自身检查 | 跨节点数据约束需要应用层保证 |
| 隔离性(Isolation) | 锁机制和MVCC | 分布式锁、全局快照难以实现 |
| 持久性(Durability) | 写入磁盘即保证 | 节点宕机后数据可能丢失,需要副本同步 |
你看这个表格就清楚了。原子性在分布式环境里,最大的问题就是「部分失败」——有的节点提交了,有的节点回滚了。我曾经在一个金融项目里遇到过这种情况:A服务扣款成功,B服务加积分时网络超时,结果钱扣了积分没加。用户投诉,我们查了半天日志才定位到问题。
2.4 XA协议:分布式事务的「老大哥」
XA协议是X/Open组织提出的分布式事务规范。它定义了一个全局事务管理器(Transaction Manager,简称TM)和多个资源管理器(Resource Manager,简称RM)之间的交互接口。
说白了,就是引入一个「协调者」来管理所有参与事务的数据库。每个数据库都实现XA接口,告诉协调者:「我准备好了」或者「我失败了」。
XA协议的核心思想是两阶段提交(2PC)。嗯,这里要注意,2PC不是XA的全部,但它是XA最经典的实现方式。
2.5 2PC模型:两阶段提交详解
2PC分为两个阶段:准备阶段(Prepare Phase)和提交阶段(Commit Phase)。我画个流程图你感受一下:
协调者(TM) 参与者1(RM1) 参与者2(RM2)
| | |
|-------- 准备请求 ------------>| |
| |--- 记录undo/redo日志 ---->|
| | |
|<------- 准备成功 ------------| |
| | |
|-------- 准备请求 ------------>| |
| | |
|<------- 准备成功 --------------------------------------|
| | |
|-------- 提交请求 ------------>| |
| |--- 提交事务 ------------->|
| | |
|<------- 提交成功 ------------| |
| | |
|-------- 提交请求 ------------>| |
| | |
|<------- 提交成功 --------------------------------------|
第一阶段:准备阶段
- 协调者向所有参与者发送准备请求
- 参与者执行事务,但不提交
- 参与者将undo和redo日志写入磁盘
- 参与者回复「可以提交」或「不能提交」
第二阶段:提交阶段
- 如果所有参与者都回复「可以提交」,协调者发送提交请求
- 如果有任何一个参与者回复「不能提交」,协调者发送回滚请求
- 参与者收到提交或回滚请求后,执行相应操作
你想想看,这个模型看起来挺完美的对吧?但实际用起来,坑不少。
- 同步阻塞: 准备阶段,参与者会锁住资源。我在项目中遇到过,一个慢查询导致整个事务卡住,其他请求全部排队等待。
- 单点故障: 协调者挂了,所有参与者都处于「悬而未决」的状态。我曾经在凌晨三点被叫起来处理这种问题——协调者宕机,数据库里一堆未提交的事务。
- 数据不一致: 第二阶段,如果协调者发送了提交请求,但部分参与者没收到,就会出现部分提交、部分未提交的情况。
我记得有一次,我们用XA协议做跨库转账。测试环境一切正常,上线后遇到网络抖动,结果A库提交了,B库没收到提交指令。最后只能手动查日志,一条一条补数据。嗯,从那以后,我对2PC就多了一份敬畏。
2.6 什么时候该用XA/2PC?
说实话,现在纯XA协议用得少了。因为它的性能开销大,而且对数据库有要求(必须支持XA)。但在某些强一致性场景下,它还是不可替代的。
我个人建议:
- 如果业务对一致性要求极高(比如金融核心账务),且数据量不大,可以考虑XA
- 如果追求高并发、高可用,建议用TCC、Saga等柔性事务方案
- 如果只是简单的跨库操作,可以考虑用消息队列做最终一致性
好了,这一章的内容就到这里。下一章我们会深入聊聊TCC模式,那是一种更轻量、更灵活的分布式事务方案。到时候我会结合一个实际的订单系统案例,带你手写TCC代码。