没有事务处理就不可能保持数据的一致性。跨越多个服务的操作,对于事务管理提出了更高的要求。
微服务架构下的事务管理
我们熟知的一些编程框架和函数提供了API,用于显示的开始、提交或回滚事务。例如,Spring框架提供了注解的方式,采用@Transactional来让方法调用,自动的在事务范围内完成。
在单体应用中,只访问一个数据库,事务管理是简单明了的。
但是对于,微服务框架下,每个服务都有自己的数据库,在这种情况下。就需要跟尾高级的事务管理机制来管理事务。
分布式事务的挑战
在多个服务、数据库和消息代理间维护数据一致性的传统方式是,分布式事务。它采用两阶段提交(two phase commit, 2PC)来保证食物中所有参与方同时完成提交,或者失败时同时回滚。
弊端:
- 分布式事务, 的事实标准X/Open XA。实现分布式事务,要求数据库,消息代理,数据库驱动,消息API,都支持XA标准。一些NoSQL数据库(MongoDB),和流行的消息代理(RabbitMQ,Apache Kafka),不支持分布式事务。
- 分布式事务,本质是同步进程间通信。会降低可用性。
使用Saga模式维护数据一致性
Saga是一种在微服务架构中维护数据一致性的机制。
Saga由一连串的本地事务组成,每个本地事务负责更新自己的私有数据库,这些操作仍旧依赖于我们所熟知的ACID事务框架和函数库。
Saga使用补偿是无来回滚所作出的改变。
传统ACID模式的一个重要特性是,如果业务逻辑检测到违反业务规则,可以轻松回滚事务。
而Saga使用补偿事务,进行回滚。
Saga包含三种类型的事务:
- 可补偿性事务,可以使用补偿事务回滚
- 关键性事务,Saga执行的关键点,是最后一个可补偿是无或者第一个可重复的事务
- 可重复性事务,在关键事务之后的事务,保证可以成功。
Saga的协调模式
协同式
协同式,把Saga的决策和执行顺序逻辑分布在Saga的每个参与方忠,他们通过交换时间的方式来进行沟通。
协同式的参与方,使用发布/订阅进行交互。这就需要考虑通信的可靠性。
- 参与方操作的原子性:去报Saga参与方将更新其本地数据库和发布下一个事件作为数据库事务的一部分。
- 消息具备标识。让其他参与方能够正确的找到数据。
好处:
- 实现简单
- 松耦合。消息的方式交互
弊端:
- 更难理解。代码不在同一个的位置。需要追踪所有服务,才能完全了解整个事务的处理过程。
- 服务之间的循环依赖关系。
- 紧耦合的风险。每个Saga的参与方都需要订阅所有影响他们的事件。代码的更新需要同步进行。
编排式
编排式,把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器,发出命令式消息给各个Saga参与方,指示他们完成具体的工作,这个类的唯一职责就是告诉Saga的参与方该做什么事情。
编排器与参与方的交互方式,也是用命令/异步相应的方式。
好处:
- 依赖关系更简单。
- 较少的耦合。每个服务只需要实现对应的API,不需要关注Saga参与方发布的事件 。
- 改善关注点隔离,简化业务逻辑。实际的业务类,例如Order,不知道Saga编排器的存在,更关注与自己的业务。
弊端:
在编排器中存在集中的过多的业务逻辑的风险。其实就是编排器成为关键节点。
解决隔离问题
首先理解ACID。
- 原子性(Atomicity)。原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)。事务前后数据的完整性必须保持一致。
- 隔离性(Isolation)。事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性(Durability)。持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
使用Saga,缺乏ACID事务中的隔离属性。这是因为,一旦事务提交,每个Saga的本地事务所做的更新都会立即被其他Saga看到。
只会导致以下情况:
- 丢失更新。其他Saga会在执行时,更改该Saga的数据。
- 脏读。其他Saga会再该Saga完成更新之前读取旧数据。
- 模糊或不可重复读。一个Saga的两个步骤读取的数据却不相同,可能会再两次读之间有另一个Saga进行了更新。
有一些对策可以处理缺乏隔离的Saga设计。
- 语义锁。应用层程序级的锁。方式是,可补偿是无在起创建或者更新记录的时候,设置标志位。这个标志标识该记录尚未提交,且可能会发生更改,用以防止其他事务处理该记录。例如约定Order的状态为*_PENDING,表示一个Saga正在处理该记录。
而对于被阻塞的Saga,1.可以给用户返回错误,让其重试,或者2.等待记录被其他Saga释放。 - 交换式更新。
- 悲观视图。重新排序Saga的步骤,以最大限度的降低由于脏读而导致的业务风险。
- 重读值。Saga在更新之前,重新读取记录,验证是否未更改。
- 版本文件。记录了对数据执行的操作,以便可以对他们进行重新排序。Saga参与方,记录收到的操作,根据收到的操作,再重新排序。
- 业务风险评级。低风险使用Saga,高风险使用分布式事务。