从 CPU 到内存、到磁盘、到操作系统、到网络,计算机系统处处存在不可靠因素。工程师和科学家努力使用各种软硬件方法对抗这种不可靠因素,保证数据和指令被正确地处理。在网络领域有 TCP 可靠传输协议、在存储领域有 Raid5 和 Raid6 算法、在数据库领域有基于 ARIES 算法理论实现的事务机制……
这篇文章先介绍单机数据库事务的 ACID 特性,然后指出分布式场景下操作多数据源面临的困境,引出分布式系统中常用的分布式事务解决方案,这些解决方案可以保证业务代码在操作多个数据源的时候,能够像操作单个数据源一样,具备 ACID 特性。文章在最后给出业界较为成熟的分布式事务框架——Seata 的 AT 模式全局事务的实现。
一、单数据源事务 & 多数据源事务如果一个应用程序在一次业务流中通过连接驱动和数据源接口只连接并查询(这里的查询是广义的,包括增删查改等)一个特定的数据库,该应用程序就可以利用数据库提供的事务机制(如果数据库支持事务的话)保证对库中记录所进行的操作的可靠性,这里的可靠性有四种语义:
原子性,A
一致性,C
隔离性,I
持久性,D
笔者在这里不再对这四种语义进行解释,了解单数据源事务及其 ACID 特性是读者阅读这篇文章的前提。单个数据库实现自身的事务特性是一个复杂又微妙的过程,例如 MySQL 的 InnoDB 引擎通过 Undo Log + Redo Log + ARIES 算法来实现。这是一个很宏大的话题,不在本文的描述范围,读者有兴趣的话可自行研究。
单数据源事务也可以叫做单机事务,或者本地事务。
在分布式场景下,一个系统由多个子系统构成,每个子系统有独立的数据源。多个子系统之间通过互相调用来组合出更复杂的业务。在时下流行的微服务系统架构中,每一个子系统被称作一个微服务,同样每个微服务都维护自己的数据库,以保持独立性。
例如,一个电商系统可能由购物微服务、库存微服务、订单微服务等组成。购物微服务通过调用库存微服务和订单微服务来整合出购物业务。用户请求购物微服务商完成下单时,购物微服务一方面调用库存微服务扣减相应商品的库存数量,另一方面调用订单微服务插入订单记录(为了后文描述分布式事务解决方案的方便,这里给出的是一个最简单的电商系统微服务划分和最简单的购物业务流程,后续的支付、物流等业务不在考虑范围内)。电商系统模型如下图所示:
在用户购物的业务场景中,shopping-service 的业务涉及两个数据库:库存库(repo_db)和订单库(repo_db),也就是 g 购物业务是调用多数据源来组合而成的。作为一个面向消费者的系统,电商系统要保证购物业务的高度可靠性,这里的可靠性同样有 ACID 四种语义。
但是一个数据库的本地事务机制仅仅对落到自己身上的查询操作(这里的查询是广义的,包括增删改查等)起作用,无法干涉对其他数据库的查询操作。所以,数据库自身提供的本地事务机制无法确保业务对多数据源全局操作的可靠性。
基于此,针对多数据源操作提出的分布式事务机制就出现了。
分布式事务也可以叫做全局事务。
二、常见分布式事务解决方案2.1 分布式事务模型
描述分布式事务,常常会使用以下几个名词:
事务参与者:例如每个数据库就是一个事务参与者
事务协调者:访问多个数据源的服务程序,例如 shopping-service 就是事务协调者
资源管理器(Resource Manager, RM):通常与事务参与者同义
事务管理器(Transaction Manager, TM):通常与事务协调者同义
在分布式事务模型中,一个 TM 管理多个 RM,即一个服务程序访问多个数据源;TM 是一个全局事务管理器,协调多方本地事务的进度,使其共同提交或回滚,最终达成一种全局的 ACID 特性。
2.2 二将军问题和幂等性
二将军问题是网络领域的一个经典问题,用于表达计算机网络中互联协议设计的微妙性和复杂性。这里给出一个二将军问题的简化版本:
一支白军被围困在一个山谷中,山谷的左右两侧是蓝军。困在山谷中的白军人数多于山谷两侧的任意一支蓝军,而少于两支蓝军的之和。若一支蓝军对白军单独发起进攻,则必败无疑;但若两支蓝军同时发起进攻,则可取胜。两只蓝军的总指挥位于山谷左侧,他希望两支蓝军同时发起进攻,这样就要把命令传到山谷右侧的蓝军,以告知发起进攻的具体时间。假设他们只能派遣士兵穿越白军所在的山谷(唯一的通信信道)来传递消息,那么在穿越山谷时,士兵有可能被俘虏。