22slug : thinking-for-distributed-transactions
33title : 对分布式事务的一些思考
44authors : [OneCastle5280]
5- tags : [分布式事务 ]
5+ tags : [分布式 ]
66---
77
88## 要求
@@ -24,7 +24,7 @@ tags: [分布式事务]
24246 . 发起支付:系统调用第三方支付网关交互以完成实际的支付过程。
25257 . 确认支付并减少库存:支付成功后,系统通知库存服务正式减少商品库存,通知积分服务正式减少可使用积分,并更新订单状态为“已支付”。
2626
27- 涉及的服务有:
27+ 涉及的服务有:
28281 . 库存服务:负责管理商品的库存。
29292 . 订单服务:负责处理用户的订单创建和对接第三方支付流程。
30303 . 积分服务:负责处理用户的积分抵扣逻辑。
@@ -44,7 +44,7 @@ tags: [分布式事务]
4444#### 事务协调方
4545分布式事务的核心角色:负责管理和协调参与事务的各个服务或资源管理器;是分布式事务的发起方,需要确保所有参与者都能一致地提交或回滚事务。
4646#### 资源方
47- 分布式事务中负责管理本地资源(例如对于库存服务来说,库存就是本地资源)、执行资源锁定、核销或回滚操作。资源方都需要实现 ` Try() ` 、` Confirm() ` 、` Cancel() ` 三个接口, 下面简述一下每个接口需要承担的责任;
47+ 分布式事务中负责管理本地资源(例如对于库存服务来说,库存就是本地资源)、执行资源锁定、核销或回滚操作。资源方都需要实现 ` Try() ` 、` Confirm() ` 、` Cancel() ` 三个接口, 下面简述一下每个接口需要承担的责任;
4848
4949> 1 . Try(): 当分布式事务开启时,事务协调方会调用所有资源方的` Try() ` 进行「资源锁定」;资源方需要保证只要` Try() ` 调用成功,后续的` Confirm ` 、` Cancel() ` 能够对「被锁定的资源」进行核销或释放。
5050> 2 . Confirm(): 用于提交分布式事务,核销前面通过` Try() ` 锁定的资源。
@@ -71,7 +71,7 @@ tags: [分布式事务]
7171> 1 . 库存服务(资源方)释放锁定库存,库存状态标记为「已释放」;
7272> 2 . 积分服务(资源方)释放锁定积分,积分状态标记为「已释放」;
7373>
74- > 被释放出来的资源则可以继续被其他用户进行锁定- 核销/释放
74+ > 被释放出来的资源则可以继续被其他用户进行锁定 - 核销/释放
7575
7676### 2.3 异常场景
7777上述都是各个服务都能够正常响应、正常处理业务逻辑的成功场景;一切都很美好,但实际上可能会存在各种各样的异常场景,例如:
@@ -83,20 +83,20 @@ tags: [分布式事务]
8383
8484#### 2.3.1 Try 阶段
8585##### 部分资源方返回成功,部分资源方返回失败
86- 情况1 :
86+ 情况 1 :
8787![ alt text] ( img/image6.png )
8888资源方明确返回资源不充足,此时事务无法开启,需要通知` Try() ` 返回成功的资源方进行资源释放,即调用 ` Cancel() ` .
8989
90- 情况2 :
90+ 情况 2 :
9191![ alt text] ( img/image7.png )
9292超时导致的失败,则此次` Try() ` 调用有可能成功到达了资源方,也有可能因为网络问题最终没有到达资源方;但是对于事务协调方来说,就是调用 ` Try() ` 失败了;需要对所有资源方调用 ` Cancel() ` 进行资源释放。
9393
94- 情况3 :
94+ 情况 3 :
9595![ alt text] ( img/image8.png )
9696` Try() ` 最终因为网络原因没有到达积分服务,此时接收到 ` Cancel() ` 请求,对于积分服务来说,没有任何资源可以支持回滚。
9797> 资源方的` Cancel() ` 接口需要能够支持空回滚。
9898
99- 情况4 :
99+ 情况 4 :
100100![ alt text] ( img/image9.png )
1011011 . 刚开始调用` Try() ` 超时了,事务协调方调用` Cancel() ` 进行资源释放。
1021022 . 在收到 ` Cancel() ` 之后,前面在网络中迷失的` Try() ` 请求又到达了积分服务;如果积分服务执行 ` Try() ` 成功,就会把资源给锁定了,并且对于事务协调方来说,分布式事务已经执行完成了,不会再有后续的 ` Confirm() ` /` Cancel() ` 来对资源核销或者释放了;这个是因为网络原因导致的 ** 乱序问题** 。
@@ -110,9 +110,9 @@ tags: [分布式事务]
110110![ alt text] ( img/image10.png )
111111对于事务协调方来说,调用结果是失败的,积分服务是否成功无法感知,但是事务协调方不可能一直等待某个资源方的 ` Confirm() ` 响应成功。
112112
113- 此时,可以通过引入消息队列组件, 利用消息队列能够确保消息最低能够被消费一次的特性,让资源方自行监听消息,收到` Confirm ` 的消息,自行调用 ` Confirm() ` 逻辑, 完成` Confirm ` 阶段。如下图所示:
113+ 此时,可以通过引入消息队列组件, 利用消息队列能够确保消息最低能够被消费一次的特性,让资源方自行监听消息,收到` Confirm ` 的消息,自行调用 ` Confirm() ` 逻辑,完成` Confirm ` 阶段。如下图所示:
114114![ alt text] ( img/image11.png )
115- 这里同样有一个问题:订单服务(事务协调方)和消息队列 Broker同样是部署在不同的节点上 ,同样存在不确定性,如何确保消息一定发送出来呢?例如下列场景:
115+ 这里同样有一个问题:订单服务(事务协调方)和消息队列 Broker 同样是部署在不同的节点上 ,同样存在不确定性,如何确保消息一定发送出来呢?例如下列场景:
116116
117117> 1 . Try 阶段完成,库存锁定成功、用户积分锁定成功、对应的订单创建成功,并且订单状态为「未支付」。
118118> 2 . 用户完成支付,此时事务要进入` Confirm ` 阶段,开始调用资源方的 ` Confirm() ` 接口进行事务提交。
@@ -124,7 +124,7 @@ tags: [分布式事务]
124124
125125![ alt text] ( img/image12.png )
126126
127- > 1 . 在调用资源方 ` Confirm() ` 接口出现异常的时候,借助消息队列最少能消费一次的特性,发送「补偿」消息,延后通知资源方进行核销, 实现最终一致性;并且此时订单状态更新为「已支付」。
127+ > 1 . 在调用资源方 ` Confirm() ` 接口出现异常的时候,借助消息队列最少能消费一次的特性,发送「补偿」消息,延后通知资源方进行核销, 实现最终一致性;并且此时订单状态更新为「已支付」。
128128> 2 . 如果发送「补偿」消息失败,则生成一条状态为「待发送」的消息记录到数据库,标识事务还有一个「补偿」消息没有完成发送;并且将存储「补偿」消息的动作和更新订单状态为「已支付」放到** 同一个本地事务** 里,要么一起成功、要么就一起失败;
129129> 3 . 然后另起一个定时任务来扫描「本地消息表」里「待发送」的消息记录,然后去做补偿发送消息。
130130
0 commit comments