当前位置:   article > 正文

微服务架构下事务解决方案_微服务事务

微服务事务

本文是对《微服务架构设计模式》一书中的笔记整理。

1. 事务分类

1.1 单体事务

所有的数据操作都在本地服务中完成,就是我们常说的ACID事务。

很多单体架构通常不适用严谨的ACID事务,而是使用较低的隔离级别来提高性能。

1.2 分布式事务

在微服务架构下,一个事务内包含的所有操作分散在多个服务中。

2. 分布式事务实现方案

2.1 基于XA模式

XA(两阶段提交)来保证参与方同时提交,或在失败时同时回滚。但是许多技术不支持XA标准的分布式事务,如MongoDB、RabbitMQ;它们本质上是同步的进程间通信,这会降低分布式系统可用性。

2.2 异步通信机制Saga

Saga是一种分布式事务的实现思路,将进程间的同步进程通信改为基于事件发布/订阅的异步通信。

3. Saga的实现

3.1 核心步骤

  1. 当本地事务完成后,服务会发布消息,触发下一步操作。
  2. Saga每一步都将其更改提交至数据库,若第n+1步失败了,服务会发布消息,将回滚前n个步骤。

3.2 Saga的三种类型事务

  • 可补偿性事务

可以通过补偿事务回滚的事务,其中只读步骤或者之后操作总是成功的步骤,不需要设计为可补偿性事务。

  • 关键性事务

如果关键事务运行成功,那么Saga将一直运行到完成。关键事务不一定是可补偿性事务或者可重复性事务。它可以是最后一个可补偿性事务或者第一个可重复性事务。它是Saga执行过程的关键点。

  • 可重复性事务

在关键事务之后的事务,保证成功。

3.3 Saga的实现模式

  • 协同式

把Saga的决策和执行顺序逻辑分布式在Saga的每一个参与方中,它们互相通过交换事件来沟通。

  • 编排式

把Saga的决策和执行顺序逻辑集中在一个Saga编排器中,Saga编排器可以是其中一个参与方。

简单的Saga可以使用协同式,但编排式通常是复杂Saga更好的选择。

不管使用哪种方式,事件发布都起着关键角色的作用,所以我们要保证可靠的事件通信。

第一个问题是要确保Saga参与方将更新其本地数据库和发布事件作为数据库事务的一部分。

第二个问题是Saga参与方必须能够将接收到的每个事件映射到自己的事件上。

3.4.1 编排式Saga

3.4.1.1 设计思路

编排器对其中某个参与方发出命令式消息,告诉这个参与方该做什么操作。当参与方完成操作后,会给编排器发送答复消息。编排器处理这个消息,并决定Saga的下一步操作是什么。

3.4.1.2 设计要点
  1. 为了保证一致性,Saga的编排器就是其中一个参与方创建的,两者之间的通信也是事件。
  2. 状态机是建模Saga编排器的一个好方法,状态机是由一组状态和由事件触发的状态之间的转换而成。
  3. 每个服务供编排器调用的API,因此它不需要知道其它Saga参与方发布的事件。

3.4 Saga的挑战

  1. Saga参与方之间缺乏隔离。
  2. 确保Saga更新本地数据和发布事件一起成功。
  3. 发生错误时要回滚更改。

3.5 Saga隔离性问题

3.5.1 缺乏隔离导致的问题

  • 丢失更新

一个Saga没有读取更新,而是直接覆盖了另一个Saga的更改

  • 脏读

一个事务或者一个Saga读取了尚未完成的Saga所做的更改

  • 模糊或不可重复读

一个Saga的两个步骤读取相同的数据却获取了不同的结果,因为另一个Saga已经进行了更新。

由于每个Saga参与方的本地事务提交,所做的更改都会立即被其它Saga看到,可以认为Saga只满足ACD,不满足隔离性。

3.5.2 实现隔离的策略

以一种能够防止异常或者最小化其对业务影响的方式来编写Saga。

在应用程序实现Saga时,可能使用多种对策,也可能使用基于XA的分布式事务,依据于业务风险评估。

3.5.2.1 语义锁

语义锁是约定好的具有锁作用的标记,无强制约束力。

Saga的可补偿性事务会在其创建或更新的任何记录中设置标记,该标记表示该记录未提交且可能发生更改,该标记可以是阻止其它事务访问记录的锁,也可以是指示其它事务应该谨慎地处理该记录的一个警告。这个标记会被一个可重复性事务清除,表示Saga成功完成。或者通过补偿性事务清除,表示Saga发生了回滚。

案例:

  1. order.state字段标记为*_PENDING状态,表示当前order对象正处于修改中。
  2. create order Saga第一步先将state改为approval_pending(审批进行中)。
  3. create order Saga的最后一步是将该字段更改为approved(审批通过),是一个可重复性事务
  4. 补偿性事务将字段改为rejected(审批拒绝)。

Saga参与方遇到语义锁怎么办?此时可以直接返回失败并告诉客户端重试,或者阻塞等待,直到Saga释放了锁,这种情况下,应用程序要管理锁,做死锁检测。

3.5.2.2 悲观视图

它重新排序Saga的步骤,以最大限度地降低由于脏读而导致的业务风险。

3.5.2.3 重读值

Saga在更新之前重新读取记录,验证它是否未更改,然后更新记录,类似于乐观锁

3.5.2.4 版本文件

它记录了对数据执行的操作,以便可以对它们重新进行排序,类似于MySQL的MVCC。

例如:create order Saga 与cancel order Saga同时执行,一个去要操作信用卡授权,一个要操作取消信用卡授权。若取消信用卡授权请求先到达,则记录在版本文件中,当信用卡授权信息再到达时,它会注意到已经收到了取消信用卡授权的请求,则跳过信用卡授权。

3.6 代码实现编排式Saga

示例:创建订单,以下列举了核心类职责

  • CreateOrderSaga:Saga编排器,定义Saga状态机,它调用CreateOrderSagaState来创建命令式消息,并使用Saga参与方代理类指定的消息通道,将它们发送给参与方。
  • CreateOrderSagaState:一个Saga的持久状态,用于创建命令式消息。
  • Saga参与方代理类:例如KitchenServiceProxy,每个代理类定义一个Saga参与方的消息API,它由命令通道、命令式消息类型和回复类型组成,包含执行动作、收到成功回复、收到失败回复后动作。
  • SagaManager:用于处理持久化Saga,生成命令式消息、订阅消息回复、并调用Saga来处理回复

4. 可靠的事件通信

  • 确保Saga参与方将更新其数据库与发布事件作为数据库事务的一部分
  1. 数据库表更新
  2. 使用消息库表作为消息队列,将要发送的消息先持久化至数据库表中
  3. 保证1和2在一个数据库事务中,发送消息前先保证库表持久化成功
  4. 通过轮询步骤2的数据库表或者监听数据库日志,异步发送消息。
  • 确保Saga参与方必须能够通过接收到的每个事件映射到自己的数据上

方案1:Saga参与方发布包含相关ID的事件,该相关性ID使其参与方能够执行数据操作。

方案2:在事务消息中包含消息接收方所需的所有消息。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/813809
推荐阅读
相关标签
  

闽ICP备14008679号