分布式事务两阶段提交(2PC)

注意
本文最后更新于 2023-06-26,文中内容可能已过时。

分布式事务中的2PC(Two-Phase Commit,二阶段提交)是一种为了保证分布式系统数据一致性而设计的算法。

本地事务

大多数场景下,应用只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。

在JDBC编程中,通过java.sql.Connection开启、关闭或者提交事务。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Connection conn = ... //获取数据库连接
conn.setAutoCommit(false); //开启事务
try{
   //...执行增删改查sql
   conn.commit(); //提交事务
}catch (Exception e) {
  conn.rollback();//事务回滚
}finally{
   conn.close();//关闭链接
}

分布式事务

在微服务架构中,完成一个业务功能可能涉及多个服务和数据库。这需要用到分布式事务,确保对多个资源服务器的数据操作要么都成功,要么都失败,以保持数据一致性。

典型的分布式事务应用场景

跨库事务

跨库事务就是一个功能需要同时操作几个数据库来完成。 如图:

graph LR;
A[(Database A)]
B[(Database B)]
S(Service)
S --> A
S --> B
subgraph Transaction
A
B
end

分库分表

当数据库的数据量庞大或预计未来会大幅增长时,我们通常会进行分库分表。如下图所示,数据库B已被拆分为两个库。

graph LR;
A[(数据库A)] & B1[(数据库B 分区1)] & B2[(数据库B 分区2)]
S(Service)
S --> A & B1 & B2
subgraph Transaction
    A & B1 & B2
    subgraph 数据库B
        B1 & B2
    end
end

分库分表时,我们常用数据库中间件简化复杂的SQL操作。例如,原本的单库插入语句如insert into user(id,name) values (1,'张三'),(2,'李四')在单库中可确保事务一致性。 但分库分表后,我们希望将1号记录存到分库1,2号记录存到分库2。 因此,中间件需改写为两条SQL,分别插入两库。为确保两库操作要么全成功,要么全失败,中间件都需解决分布式事务的难题。

微服务架构

graph LR;
sa(Service A) & sb(Service B) & sc(Service C)
d1[(数据库)] & d2[(数据库)] & d3[(数据库)] & d4[(数据库)]
sa -.-> sb -.-> d1 & d2
sa -.-> sc -.-> d3
sa -.-> d4

subgraph Transaction
    d1 & d2 & d3 & d4
end

Service A在执行任务时,既要直接操作数据库,又要调用Service B和Service C。而Service B会操作两个数据库,Service C操作一个数据库。 关键是要确保这些跨服务的数据库操作全部成功,或者全部失败,这实际上是分布式事务的一个经典场景。

小结
在分布式事务场景中,通常会涉及多个数据库的操作。确保事务的ACID特性在实现上颇具挑战。同时,分布式事务方案还需兼顾性能,否则即便能严格保证ACID特性,但若性能大幅下滑,对要求快速响应的业务来说也是不可接受的。

思路:两阶段提交协议(2PC)

两阶段提交(Two Phase Commit),就是将提交(commit)过程划分为2个阶段(Phase)

阶段1:

事务管理器(TM)会告知各个资源管理器(RM)准备提交它们负责的事务部分。 如果RM确认自己没问题,就会保存更改并告知TM一切就绪;若有任何问题,则会向TM发出否定反馈。

以MySQL为例,在事务的第一阶段,事务管理器会向所有相关数据库发出“准备提交”的请求。数据库接收请求后,会进行数据变更和日志记录等操作。完成后,它只会更改事务状态为“可提交”,并立即通知事务管理器。

阶段2:

事务管理器根据资源管理器的准备结果,决定提交或撤销事务。全成功则提交,有失败则全撤销。

若MySQL数据库准备成功,事务管理器会要求提交。否则,若有任何问题,事务管理器会撤销所有事务。数据库若未收到提交指令,也会自行撤销。

分布式事务不可能100%解决,只能尽量提高成功率,比如99.99%。

graph LR;
order(订单服务) & orderDB[(订单数据库)] & storeDB[(库存数据库)] & tm(事务管理器(中间协调者))
order --> orderDB & storeDB -.->|p1:预提交| tm -.->|p2:提交/回滚| orderDB & storeDB
subgraph Database
    storeDB & orderDB
end

Note[存在问题
1. 库存不够减少
2. 库存库挂了]

两阶段提交方案中,全局事务的ACID特性由资源管理器(RM)确保。全局事务包含多个事务分支,它们必须一起成功或一起失败。每个分支的ACID属性共同支撑全局事务的ACID,从而将ACID从单一事务扩展到分布式事务。

2PC存在的问题

  • 同步阻塞问题:2PC 中的参与者是阻塞的。在第一阶段收到请求后就会预先锁定资源,一直到 commit 后才会释放。
  • 单点故障:若协调者TM出问题,参与者RM将停滞不前。特别是第二阶段,若协调者出故障,所有参与者的事务资源将被锁定,导致事务无法推进。
  • 数据不一致:协调者若在发送提交请求时崩溃,可能会导致数据错乱。因为可能有部分参与者已经按指示提交了事务,而其他参与者却因未收到指示而中止事务,这样数据就不一致了。