两阶段提交协议
Two-Phase Commit,2PC
,wiki,顾名思义分成两个阶段,先由一方提议并收集其他节点的反馈(准备阶段Propose),再根据反馈决定提交或终止事务(执行阶段Commit)。一般将提议的节点称为协调者Coordinator,其他参与决议的节点称为参与者Participants,如图展示了协调者发起一个提议分别询问各参与者是否接受的场景,然后协调者根据参与者的反馈,提交或中止事务。如果参与者全部同意则提交,只要有一个参与者不同意就中止
概括
参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
缺点
- 同步阻塞问题
执行过程中,所有参与节点都是事务阻塞型的。当参与者占用公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态 - 单点故障
在2PC中协调者很重要,一旦协调者出现问题,参与者就会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作 - 数据不一致
在二阶段提交中,如果协调者发生了故障,导致只有部分参与者收到了提交请求。接收到请求的参与者会执行提交操作,但是其他未收到提交请求的参与者则无法执行事务提交。于是整个分布式系统便出现了数据不一致的现象
三阶段提交协议
Three-phase commit,3PC
,wiki,三阶段提交协议是二阶段协议的改进版,与二阶段提交相比,三阶段提交有两个改动点,其一是同时在协调者和参与者中都引入超时机制,其二则是把二阶段提交中的准备阶段再次一分为二,这样三阶段提交就有CanCommit
,PreCommit
和DoCommit
三个阶段。
三阶段提交
- 一阶段-
CanCommit
如图,CanCommit阶段其实和2PC的准备阶段很像,协调者向参与者发送提交请求,参与者如果可以提交就返回Yes相应,否则就返回No响应 - 二阶段-
PreCommit
如图,在PreCommit阶段,协调者根据参与者的响应情况来决定是否可以进行事务的PreCommit操作。
有以下两种可能:假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行;假如有任何一个参与者向协调者发送了No响应,或者等待超时之后协调者都没有接到参与者的响应,那么就执行事务的中断 - 三阶段-
DoCommit
如图,DoCommit阶段是真正的事务提交阶段,也可以分为以下两种情况:执行事务和中断事务。
当协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务
相对于2PC,3PC主要解决的是单点故障问题,并减少阻塞。因为一旦参与者无法及时收到来自协调者的信息就会模式执行提交,而不会一直持有事务资源并处于阻塞状态
最终一致性
分布式事务能解决一部分数据一致性问题,但在微服务架构中,传统分布式事务并不是实现数据一致性的最佳选择,当下主流的分布式应用大都不会锁定被修改的数据,而是采用一种更为松散的方式来维护一致性,也就是所谓的最终一致性(Eventual Consistency)。
当然,在概念上说明最终一致性模型很简单,但开发者必须要保证系统最后的一致性。换言之,无论是所有的步骤全部执行完毕或者是有的步骤失败时,必须要保证不会影响系统的最终状态的一致性。开发者实现最终一致性的方案可以根据不同的业务场景做不同的选择
可靠事件模式
可靠事件模式属于事件驱动架构,微服务完成操作后向消息代理发布事件,关联的微服务从消息代理订阅到该事件从而完成相应的业务操作,关键在于可靠事件投递和避免事件重复消费。
基本思路
以订单和支付的微服务为例
-
用户下单
如图,当用户使用订单服务下单时,一方面订单服务需要对所产生的订单数据进行持久化操作,另一方面,它也需要同时发送一条创建订单的消息到消息中间件
-
交易支付
如图,当消息中间件接收到订单消息,就会把该消息发送到支付服务,支付服务进行相应的操作,业务逻辑执行完成之后,支付服务需要向消息中间件发送一条支付成功消息
-
订单更新
支付成功消息通过消息中间件传递订单服务时,订单服务根据支付结果处理后续业务流程,一般会涉及订单状体的更新、向用户发送通知等内容
问题
该模式可能会出现两个问题
- 重复消费
重复消费场景,一般的处理方法是由业务代码控制幂等性(Idempotency)。例如,在支付服务中传入一个订单时,可以通过判断该订单所对应的唯一Id是否已经处理的方式避免对其再次处理。 - 消息传递的可靠性
用户下单可以看出订单服务需要处理数据持久化和消息发送两个操作,这就需要考虑两种场景- 数据持久化失败,消息不应该发送
-
数据持久化成功,消息发送发送失败,数据持久化需要回滚
- 产生问题
该流程可能会出现如下问题- 网络通信引起数据不一致
如当消息投递成功但返回时可能会发生网络异常,会导致数据不一致 - 服务不可用导致数据不一致
当订单服务投递消息之后等待消息返回,但当消息真正返回时,订单服务自身发生错误导致其不可用,会导致数据不一致
- 网络通信引起数据不一致
解决方案
- 本地事件表
可靠事件模式对以上场景的解决方案就是使用一个本地事件表。
微服务在进行业务操作时需要将业务数据和事件
保存在同一个本地事务中,由本地事务保证更新业务和发布事件的原子性。发布的事件被保存在本地事件表中,然后该服务实时发布一个事件通知关联的业务服务,如果事件成功发布则立即删除本地事件表中的事件。 - 事件恢复
事件消息的发布可能会失败或者无法获取返回结果,我们需要使用一个额外的“事件恢复”服务来恢复事件,事件恢复服务定时从事件表中恢复未发布成功的事件并重新发布,只有重新发布成功才删除保存在本地事件表中的事件。
事件恢复服务保证了事件一定会被发布,从而确保数据的最终一致性。
注
:由于事件恢复机制,可能会导致支付服务消息重复接收,因此幂等性就显得格外重要
实现策略
如图,展示了可靠事件模式实现架构,可靠事件模式中存在一个事件生产者和事件消费者
- 事件生产者
事件生产者处于操作的主导地位,它根据业务操作通过事件的方式发送业务操作的结果 - 事件消费者
事件消费者是被动方,负责根据事件来处理自身业务逻辑
事件服务的主要作用就是管理本地事件表,它能够存储、确认并发送事件,同时根据不同状态查询事件信息并确定事件已被事件消费者成功消费。事件服务由三个部分组成
- 事件确认组件
事件确认表现为一种定时机制,用于处理事件没有被成功发送的场景
例如,在事件生产者在完成业务操作以后需要发送事件到本地事件表,如果这个过程中事件没有发送成功,就需要对这些事件进行重新发送,这个过程称为事件确认
- 事件恢复组件
事件恢复组件同样是一种定时机制,根据本地事件表中的事件状态,专门处理状态为已确认但还没有被成功消费且已超时的事件
基本的事件恢复策略就是向消费者重新发送事件,并在消费者成功消费之后通过更新事件状态的方式将该事件在本地事件表中进行逻辑删除 - 实时消息传递组件
实时消息传递组件就是基于特定的消息中间件工具和框架将事件作为消息进行发送的组件,如RabbitMQ、ActiveMQ、RocketMQ、Kafka等
事务补偿模式
Compensating Transaction Pattern
,wiki
基本思路
事务补偿模式的基本思路在于使用一个额外的补偿服务
来协调各个需要保证一致性的微服务,补偿服务按顺序依次调用各个微服务,如果某个微服务调用失败就撤销之前所有已经完成的微服务。在这个过程中,补偿服务对需要保证一致性的微服务提供补偿操作
关键点
对于补偿服务而言,所有微服务的操作记录是一个关键点,因为操作记录是执行取消操作的前提,理论上说可根据唯一的业务流水号即可完成补偿操作,但是提供更多的数据有益于微服务的健壮性
解决方案
实现补偿模式的关键要素是记录完整的业务流水,可以通过业务流水的状态来确定需要补偿的步骤,同时业务流水为补偿操作提供需要的业务数据。补偿服务可以从业务流水的状态中知道补偿的范围,补偿过程中需要的业务数据同样也可以从记录的业务流水中获取
注
:补偿服务作为一个服务调用过程同样存在调用不成功的情况,这时就需要一定的健壮性机制来保证补偿的成功率,也就是说补偿服务的相关操作本身就应该具备幂等性。
网友评论