二将军问题是网络领域的一个经典问题,用于表达计算机网络中互联协议设计的微妙性和复杂性。这里给出一个二将军问题的简化版本: 一支白军被围困在一个山谷中,山谷的左右两侧是蓝军。困在山谷中的白军人数多余山谷两侧的任意一支蓝军,而少于两支蓝军的之和。若一支蓝军对白军单独发起进攻,则必败无疑;但若两支蓝军同时发起进攻,则可取胜。两只蓝军的总指挥位于山谷左侧,他希望两支蓝军同时发起进攻,这样就要把命令传到山谷右侧的蓝军,以告知发起进攻的具体时间。假设他们只能派遣士兵穿越白军所在的山谷(唯一的通信信道)来传递消息,那么在穿越山谷时,士兵有可能被俘虏。
只有当送信士兵成功往返后,总指挥才能确认这场战争的胜利(上方图)。现在问题来了,派遣出去送信的士兵没有回来,则左侧蓝军中的总指挥能不能决定按命令中约定的时间发起进攻? 答案是不确定,派遣出去送信的士兵没有回来,他可能遇到两种状况: 1)命令还没送达就被俘虏了,这时候右侧蓝军根本不知道要何时进攻; 2)命令送达,但返回途中被俘虏了,这时候右侧蓝军知道要何时进攻,但左侧蓝军不知道右侧蓝军是否知晓进攻时间。 类似的问题在计算机网络中普遍存在,例如发送者给接受者发送一个 HTTP 请求,或者 MySQL 客户端向 MySQL 服务器发送一条插入语句,然后超时了没有得到响应。请问服务器是写入成功了还是失败了?答案是不确定,有以下几种情况: 1)可能请求由于网络故障根本没有送到服务器,因此写入失败; 2)可能服务器收到了,也写入成功了,但是向客户端发送响应前服务器宕机了; 3)可能服务器收到了,也写入成功了,也向客户端发送了响应,但是由于网络故障未送到客户端。 无论哪种场景,在客户端看来都是一样的结果:它发出的请求没有得到响应。为了确保服务端成功写入数据,客户端只能重发请求,直至接收到服务端的响应。 类似的问题问题被称为网络二将军问题。
两阶段提交(2PC)
准备阶段:事务协调者向各个事务参与者发起询问请求 提交阶段:如果各个参与者都回复yes,则协调者向所有参与者发起事务提交操作,然后参与者收到各自执行的本地事务提交操作并向 协调者发送ACK;任何一个参与者回复no或超时,则协调者向所有参与者发起事务回滚操作。 存在的问题: 在提交阶段,协调者向所有的参与者发送了提交指令,如果一个参与者未返回 ACK,那么协调者不知道这个参与者内部发生了什么(由于网络二将军问题的存在,这个参与者可能根本没收到提交指令,一直处于等待接收提交指令的状态;也可能收到了,并成功执行了本地提交,但返回的 ACK 由于网络故障未送到协调者上),也就无法决定下一步是否进行全体参与者的回滚。
三阶段提交(3PC)
询问阶段 准备阶段 提交阶段/回滚阶段 3PC利用超时机制解决了2PC的同步阻塞问题。 存在的问题: 但是3PC同样无法应对类似宕机或者的问题,只不过出现数据不一致的问题更小
本地消息表
本地消息表方案的核心思路是将分布式事务拆分成本地事务进行处理。通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。
上图中整体的处理步骤如下:
- 1.事务主动方在同一个本地事务中处理业务和写消息表操作;
- 2.事务主动方通过消息中间件,通知事务被动方处理事务通知事务待消息。消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息;
- 3.事务被动方通过消息中间件,通知事务主动方事务已处理的消息;
- 4.事务主动方接收中间件的消息,更新消息表的状态为已处理。
MQ事务方案
最大努力通知方案是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。 其适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。