分布式事务两阶段提交(2PC)
分布式事务中的2PC(Two-Phase Commit,二阶段提交)是一种为了保证分布式系统数据一致性而设计的算法。
本地事务
大多数场景下,应用只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。
在JDBC编程中,通过java.sql.Connection开启、关闭或者提交事务。
|
|
分布式事务
在微服务架构中,完成一个业务功能可能涉及多个服务和数据库。这需要用到分布式事务,确保对多个资源服务器的数据操作要么都成功,要么都失败,以保持数据一致性。
典型的分布式事务应用场景
跨库事务
跨库事务就是一个功能需要同时操作几个数据库来完成。 如图:
graph LR; A[(Database A)] B[(Database B)] S(Service) S --> A S --> B subgraph Transaction A B endgraph 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 endgraph 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 endgraph 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操作一个数据库。 关键是要确保这些跨服务的数据库操作全部成功,或者全部失败,这实际上是分布式事务的一个经典场景。
思路:两阶段提交协议(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. 库存库挂了]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将停滞不前。特别是第二阶段,若协调者出故障,所有参与者的事务资源将被锁定,导致事务无法推进。
- 数据不一致:协调者若在发送提交请求时崩溃,可能会导致数据错乱。因为可能有部分参与者已经按指示提交了事务,而其他参与者却因未收到指示而中止事务,这样数据就不一致了。