大家好,我是连边。
今天这篇文章给大家讲解广义上的两阶段提交。
阅读本篇文章大约需要10分钟。
文章分为理论和实战部分,理论部分讲述两阶段提交的概念与实现方式;通过理论的铺垫之后,实战部分我们会动手写代码、创建数据库,实战一个银行转账的案例,希望通过这个案例,更通透理解两阶段提交。
文章导读

理论概述
分布式一致性
分布式场景下,多个服务同时对服务一个流程,比如电商下单场景,需要支付服务进行支付、库存服务扣减库存、订单服务进行订单生成、物流服务更新物流信息等。如果某一个服务执行失败,或者网络不通引起的请求丢失,那么整个系统可能出现数据不一致的原因。
上述场景就是分布式一致性的问题,其根本原因在于数据的分布式操作,引起本地事务无法保障数据的原子性。
分布式一致性问题的解决思路有两种,一种是分布式事务,一种是尽量通过业务流程避免分布式事务。
分布式事务是直接解决问题,而业务规避其实是通过解决出问题的地方。
在真实的业务场景中,最优雅的解决方案就是业务规避。
分布式事务分类
分布式事务实现方案从类型上分为刚性事务和柔性事务。
刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务(XA协议(2PC、JTA、JTS)、3PC)
;
柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务(TCC/FMT、Saga(状态机模式,Aop模式)、本地事务消息、消息事务(半消息)、最多努力通知型事务)
;
2PC定义(二阶段定义)
是指在```计算机网络```以及```数据库```领域内,为了使基于```分布式系统```架构下所有节点在进行事务提交时保持一致性而设计的一种算法。通常,二阶段提交也被称为一种协议。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| 在我们的分布式系统中,每个节点虽然可以知道自己的操作成功或者失败,但是不知道其他的节点的成功或者失败,当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终决定这些节点是否要进行真正的提交。这里可以回忆下redo log和binlog的两阶段提交。
因此,二阶段提交的算法思路可以概括为:**参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈结果决定各参与者是否要```提交操作```还是```回滚操作```。**
## 第一阶段(提交请求阶段)
1. 协调者节点向所有参与者节点询问是否可以执行提交操作,并**开始等待各参与节点的响应**; 2. 参与者节点执行协调者节点询问发起的所有事务操作; 3. 各参与节点响应协调者节点发起的询问。如果参与者节点的事务执行成功,则它返回一个“YES”消息;如果参与者节点事务执行失败,则返回一个“NO”消息。
第一阶段,也被称为**投票阶段**,即各个参与者投票是否要继续接下来的提交操作。
## 第二阶段(提交执行阶段)
### 成功则提交
当协调者节点从所有参与者节点获得的响应消息都为“YES”时:
1. 协调者节点向所有参与者节点发出“正式提交”的请求; 2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源; 3. 参与者节点向协调者节点发送“完成”消息; 4. 协调者节点收到所有参与者节点的“完成”消息后,完成事务。
### 失败则回滚
如果任一参与者节点在第一阶段返回的消息为“NO”,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
1. 协调者节点向所有参与者节点发出“回滚操作”的请求; 2. 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源; 3. 参与者节点向协调者节点发送“回滚完成”消息; 4. 协调者节点收到所有参与者节点的“完成”消息,取消事务。
第二阶段,也被称为**完成阶段**,因为无论结果怎样,协调者都必须在此阶段结束当前事务。
## 二阶段流程图

## 实战
### 实战概述
通过对二阶段提交的理论讲解,想必对什么是二阶段提交有所了解,接下来通过一个银行转账的案例,来实战一个二阶段提交,让你彻底搞懂二阶段提交。
案例:**ABC公司**为其客户提供银行转账业务,假设连边是该公司的客户,需要从连边在**中国银行的工资卡**上提取3000元到连边在**建设银行的房贷卡**上,用来还房贷。
想一想,结合上边的理论部分,这个案例中,哪些是**协调者**,哪些是**参与者**?
**ABC公司**为协调者,连边的**中国银行**和**建设银行**为参与者。
### 实战流程图

### 模块设计
**环境:**
- ```java1.8+```
- ```maven``` - ```mysql```
**模块划分:**
1. 公用模块(bank-common) 2. 客户(customer-client) 3. 建设银行服务(ccb-server) 4. 中国银行服务(boc-server) 5. ABC公司 - TM协调者(transaction-manager) 6. 协议封装(protocol)
### 数据库设计
**建设银行(ccb-server)**

**中国银行(boc-server)**

**ABC公司 - TM协调者(transaction-manager)**

### 源码
阅读源码,从```客户(customer-client)```项目入口开始阅读,关键的地方,我都加上了注释,整体源码读下来可能需要一点时间,但是对我们理解```2pc```有意义。
**阅读源码的几个关键点:**
1. 源码整体都是通过```Handler + command```的来连接处理逻辑的,如:
```java // TRANSACTION_REQ_COMMAND = transactionReq final String httpURL = getHttpURL(node, Constants.TRANSACTION_REQ_COMMAND);
|
当看到上边这段代码,你就要去找TransactionRequestHandler.class
,这里为了方便跟踪,定义了常量,idea环境可以直接通过跟踪常量跟踪到具体的代码位置。
- 采用Java并发编程中的
CountDownLatch
,阅读的时候留意下
完整的源码,关注「连边」公众号,回复2pc
即可获取,观看源码的README.md
能够运行。
运行截图:
- 打包
1 2 3
| # 需要替换自己的项目路径 cd /Library/WebServer/Documents/java/lianbian-2pc mvn -Dmaven.test.skip=true package
|

- 启动服务
1 2 3 4 5 6 7 8
| # 启动 BOC server java -Xms256M -Xmx256m -jar /Library/WebServer/Documents/java/lianbian-2pc/boc-server/target/boc-server-1.0-SNAPSHOT.jar
# 启动 CCB server java -Xms256M -Xmx256m -jar /Library/WebServer/Documents/java/lianbian-2pc/ccb-server/target/ccb-server-1.0-SNAPSHOT.jar
# 启动 Transaction Manager server java -Xms256M -Xmx256m -jar /Library/WebServer/Documents/java/lianbian-2pc/transaction-manager/target/transaction-manager-1.0-SNAPSHOT.jar
|

- 模拟转账
1 2 3
| # 模拟转账 # 参数:转出银行 转出账号 转入银行 转入账号 转账金额 java -Xms256M -Xmx256m -jar /Library/WebServer/Documents/java/lianbian-2pc/customer-client/target/customer-client-1.0-SNAPSHOT.jar BOC 1 CCB 2 20000
|


总结
这篇文章到这里就写完了,这篇文章其实很早就着手准备,但是在写的时候,还是感觉效率不高,要想一想怎么提升效率这个事情了。
完整的源码,关注「连边」公众号,回复2pc
即可获取,观看源码的README.md
能够运行。
衷心感谢每一位认真读文章的人,我是连边,专注于Java和架构领域,坚持撰写有原理,有实战,有体系的技术文章。
我是连边,专注于Java和架构领域,坚持撰写有原理,有实战,有体系的技术文章。
关注 订阅号@连边
不错过精彩文章
