distributed-transaction
简单的写一写目前学习到的分布式事务的一些相关的芝士
分布式事务
单数据源事务/多数据源头事务
在分布式场景下,一个系统由多个子系统,多个子系统之间通过互相调用来组合出更复杂的业务。在时下流行的微服务系统架构中,每一个子系统被称作一个微服务,同样每个微服务都维护自己的数据库,以保持独立性。
例如,一个电商系统可能由购物微服务、库存微服务、订单微服务等组成。购物微服务通过调用库存微服务和订单微服务来整合出购物业务。用户请求购物微服务商完成下单时,购物微服务一方面调用库存微服务扣减相应商品的库存数量,另一方面调用订单微服务插入订单记录(为了后文描述分布式事务解决方案的方便,这里给出的是一个最简单的电商系统微服务划分和最简单的购物业务流程,后续的支付、物流等业务不在考虑范围内)。
## 常见分布式事务解决方案 ### 分布式事务模型 描述分布式事务,常常会使用以下几个名词:- 事务参与者:例如每个数据库就是一个事务参与者
- 事务协调者:访问多个数据源的服务程序,例如 shopping-service 就是事务协调者
- 资源管理器(Resource Manager, RM):通常与事务参与者同义
- 事务管理器(Transaction Manager, TM):通常与事务协调者同义
在分布式事务模型中,一个 TM 管理多个 RM,即一个服务程序访问多个数据源;TM 是一个全局事务管理器,协调多方本地事务的进度,使其共同提交或回滚,最终达成一种全局的 ACID 特性。
### 二阶段提交 2PC 是一种实现分布式事务的简单模型,这两个阶段是:1)准备阶段:事务协调者向各个事务参与者发起询问请求:“我要执行全局事务了,这个事务涉及到的资源分布在你们这些数据源中,分别是……,你们准备好各自的资源(即各自执行本地事务到待提交阶段)”。各个参与者协调者回复 yes(表示已准备好,允许提交全局事务)或 no(表示本参与者无法拿到全局事务所需的本地资源,因为它被其他本地事务锁住了)或超时。
2)提交阶段:如果各个参与者回复的都是 yes,则协调者向所有参与者发起事务提交操作,然后所有参与者收到后各自执行本地事务提交操作并向协调者发送 ACK;如果任何一个参与者回复 no 或者超时,则协调者向所有参与者发起事务回滚操作,然后所有参与者收到后各自执行本地事务回滚操作并向协调者发送 ACK。
所有的参与者都需要实现三个接口:- Prepare():TM 调用该接口询问各个本地事务是否就绪
- Commit():TM 调用该接口要求各个本地事务提交
- Rollback():TM 调用该接口要求各个本地事务回滚
存在的问题:
- 性能差
- 准备阶段完成后,如果协调者宕机,那么所有的参与者都收不到提交或者回滚指令
- 脑裂,无法决定下一步是否进行全体回滚
2PC 之后又出现了 3PC,把两阶段过程变成了三阶段过程,分别是:询问阶段、准备阶段、提交或回滚阶段,这里不再详述。3PC 利用超时机制解决了 2PC 的同步阻塞问题,避免资源被永久锁定,进一步加强了整个事务过程的可靠性。但是 3PC 同样无法应对类似的宕机问题,只不过出现多数据源中数据不一致问题的概率更小。
2PC 除了性能和可靠性上存在问题,它的适用场景也很局限,它要求参与者实现了 XA 协议,例如使用实现了 XA 协议的数据库作为参与者可以完成 2PC 过程。但是在多个系统服务利用 api 接口相互调用的时候,就不遵守 XA 协议了,这时候 2PC 就不适用了。所以 2PC 在分布式应用场景中很少使用。
TCC方案
描述 TCC 方案使用的电商微服务模型如下图所示,在这个模型中,shopping-service 是事务协调者,repo-service 和 order-service 是事务参与者。
上文提到,2PC 要求参与者实现了 XA 协议,通常用来解决多个数据库之间的事务问题,比较局限。在多个系统服务利用 api 接口相互调用的时候,就不遵守 XA 协议了,这时候 2PC 就不适用了。现代企业多采用分布式的微服务,因此更多的是要解决多个微服务之间的分布式事务问题。
TCC 就是一种解决多个微服务之间的分布式事务问题的方案。TCC 是 Try、Confirm、Cancel 三个词的缩写,其本质是一个应用层面上的 2PC,同样分为两个阶段:
1)阶段一:准备阶段。协调者调用所有的每个微服务提供的 try 接口,将整个全局事务涉及到的资源锁定住,若锁定成功 try 接口向协调者返回 yes。
2)阶段二:提交阶段。若所有的服务的 try 接口在阶段一都返回 yes,则进入提交阶段,协调者调用所有服务的 confirm 接口,各个服务进行事务提交。如果有任何一个服务的 try 接口在阶段一返回 no 或者超时,则协调者调用所有服务的 cancel 接口。
这里有个关键问题,既然 TCC 是一种服务层面上的 2PC,它是如何解决 2PC 无法应对宕机问题的缺陷的呢?答案是不断重试。由于 try 操作锁住了全局事务涉及的所有资源,保证了业务操作的所有前置条件得到满足,因此无论是 confirm 阶段失败还是 cancel 阶段失败都能通过不断重试直至 confirm 或 cancel 成功(所谓成功就是所有的服务都对 confirm 或者 cancel 返回了 ACK)。
事务状态表
另外有一种类似 TCC 的事务解决方案,借助事务状态表来实现。假设要在一个分布式事务中实现调用 repo-service 扣减库存、调用 order-service 生成订单两个过程。在这种方案中,协调者 shopping-service 维护一张如下的事务状态表:
初始状态为 1,每成功调用一个服务则更新一次状态,最后所有的服务调用成功,状态更新到 3。
有了这张表,就可以启动一个后台任务,扫描这张表中事务的状态,如果一个分布式事务一直(设置一个事务周期阈值)未到状态 3,说明这条事务没有成功执行,于是可以重新调用 repo-service 扣减库存、调用 order-service 生成订单。直至所有的调用成功,事务状态到 3。
如果多次重试仍未使得状态到 3,可以将事务状态置为 error,通过人工介入进行干预。
由于存在服务的调用重试,因此每个服务的接口要根据全局的分布式事务 ID 做幂等,原理同 2.4 节的幂等性实现。
基于消息中间件的最终一致性
无论是 2PC & 3PC 还是 TCC、事务状态表,基本都遵守 XA 协议的思想,即这些方案本质上都是事务协调者协调各个事务参与者的本地事务的进度,使所有本地事务共同提交或回滚,最终达成一种全局的 ACID 特性。在协调的过程中,协调者需要收集各个本地事务的当前状态,并根据这些状态发出下一阶段的操作指令。
但是这些全局事务方案由于操作繁琐、时间跨度大,或者在全局事务期间会排他地锁住相关资源,使得整个分布式系统的全局事务的并发度不会太高。这很难满足电商等高并发场景对事务吞吐量的要求,因此互联网服务提供商探索出了很多与 XA 协议背道而驰的分布式事务解决方案。其中利用消息中间件实现的最终一致性全局事务就是一个经典方案。
在这个模型中,用户不再是请求整合后的 shopping-service 进行下单,而是直接请求 order-service 下单,order-service 一方面添加订单记录,另一方面会调用 repo-service 扣减库存。
 这种实现方式的流程是:1)order-service 负责向 MQ server 发送扣减库存消息(repo_deduction_msg);repo-service 订阅 MQ server 中的扣减库存消息,负责消费消息。
2)用户下单后,order-service 先执行插入订单记录的查询语句,后将 repo_deduction_msg 发到消息中间件中,这两个过程放在一个本地事务中进行,一旦“执行插入订单记录的查询语句”失败,导致事务回滚,“将 repo_deduction_msg 发到消息中间件中”就不会发生;同样,一旦“将 repo_deduction_msg 发到消息中间件中”失败,抛出异常,也会导致“执行插入订单记录的查询语句”操作回滚,最终什么也没有发生。
3)repo-service 接收到 repo_deduction_msg 之后,先执行库存扣减查询语句,后向 MQ sever 反馈消息消费完成 ACK,这两个过程放在一个本地事务中进行,一旦“执行库存扣减查询语句”失败,导致事务回滚,“向 MQ sever 反馈消息消费完成 ACK”就不会发生,MQ server 在 Confirm 机制的驱动下会继续向 repo-service 推送该消息,直到整个事务成功提交;同样,一旦“向 MQ sever 反馈消息消费完成 ACK”失败,抛出异常,也对导致“执行库存扣减查询语句”操作回滚,MQ server 在 Confirm 机制的驱动下会继续向 repo-service 推送该消息,直到整个事务成功提交。
存在问题,有可能中间网络问题导致不一致问题,比如order发送出了,并且repo进行deduct操作但是ack失败
通过这种设计,实现了消息在发送方不丢失,消息在接收方不被重复消费,联合起来就是消息不漏不重,严格实现了 order-service 和 repo-service 的两个数据库中数据的最终一致性。
基于消息中间件的最终一致性全局事务方案是互联网公司在高并发场景中探索出的一种创新型应用模式,利用 MQ 实现微服务之间的异步调用、解耦合和流量削峰,支持全局事务的高并发,并保证分布式数据记录的最终一致性。