分布式事务

# 分布式事务

# 分布式事务的基础理论知识

# CAP

  • Partition tolerance 分区容错性

如果存储系统只运行在一个节点上, 要么系统整个崩溃, 要么正常运行. 一旦针对同一服务的存储系统分布到多个节点, 那么整个存储系统就会存在分区的可能性. 比如两个存储节点之间联通的网络段卡, 就会形成分区, 一般来讲, 为了提高服务质量, 同意份数据存放在不同城市非常正常, 因此节点之间存在分区也很正常.

分区容错性的定义为: 除全部网络节点全部故障之外, 所有子节点集合的故障不允许导致整个系统出现不正确响应. 即时部分故障, 施加的操作也可以完成.

  • Consistency 一致性

在分布式系统中, 往往会存在多个数据库, 这里的一致性指的是用户的一次对数据的修改, 如果涉及了多个数据库, 要么全部成功, 要么全部失败. 如果一个存储系统可以保证一致性, 那么用户读写的数据可以保证是最新的, 不会发生不同客户端在不同的存储节点读取到不同副本的情况.

  • Availability 可用性

即客户端在访问数据时, 必定能够得到响应, 但系统可用并不代表存储系统的所有节点提供的数据是一致的. 往往我们会对不用的应用设定一个最长的响应时间, 超过这个时间仍没有响应的服务则称之为不可用

一个存储系统不可能同时满足以上三个特性, 只能同时满足两个, 也就是CA,CP,AP. 由于分布式系统中分区情况始终会存在, 所以分区容错性必须保证, 所以必须在 CP和AP之间做出抉择.

CP

不满足可用性, 相当于每个请求都需要在各个节点之间保证强一致性, 而出现分区情况时则会导致同步的时间无线延长.

AP

保证高可用的同时允许分区, 则需要放弃一致性. 一旦分区发生, 节点之间数据时区联系, 为了高可用, 每个节点只能使用本地数据提供服务, 这样会导致全局数据的不一致性. PS: 在涉及钱的业务场景中, 尽量不要采用AP

# ACID

ACID 指的是在数据库中事务所具有的四个特性: 原子性(Atomicity), 一致性(Consistency), 隔离性(Isolation), 持久性(Durability).

# 原子性

一个事务中的操作要么全部完成, 要么全部不完成. 如果事务过程中出现错误, 要回滚到事务开始前的状态.

# 一致性

一个事务执行前后数据库应该处于一致性状态. 如果事务成功的完成, 那么所有数据都应该从一个有效的状态转移到另一个有效的状态, 如果过程中出现错误, 所有的状态变化应该自动的回滚到原始状态

# 隔离性

在并发环境中, 当不同的事务同时操作相同数据时, 每个事务都有各自的完整数据空间, 由并发事务所做的修改必须与其他并发事务所做的修改隔离. 事务查询时, 数据所处的状态要么是另一个事务修改它之前的状态, 要么是另一个事务修改它之后的状态, 事务不能查看到中间状态的数据.

# 持久性

指的是只要事务成功结束, 那么他对数据库所做的更新就必须永久保存下来, 数据库重启后也能恢复到事务成功结束时的状态.

# BASE

BASE全称为BasicallyAvailable(基本可用), Soft-state(软状态/柔性事务), Eventually Consistent(最终一致性). Base模型在理论逻辑上相反与ACID, 它牺牲了强一致性, 获得了可用性与分区容错性.

# 两阶段提交

执行步骤

  1. 一段: 事务管理器通知所有子事务准备进行操作
  2. 二段: 事务管理器通知所有子事务开始提交

一段或二段中有任意环节失败, 整体事务全部取消或回滚

两段式提交中, 事务中所有的资源都是被锁定的. 这种情况只适合执行时间确定的短事务. 但是为了保证分布式事务的一致性, 大都是采用串行化的隔离级别来保证事务一致性. 两段式提交解决了分布式存储系统中的数据强一致性问题, 但缺点也是显而易见的: 一旦事务管理器(事务的协调者)在所在服务一旦故障, 将会影响整个数据集群的正常工作. 比如事务管理器因故障不能 正常发送事务提交或回滚通知, 将会导致事务参与者们一直处于阻塞状态. 在两段式提交中, 所有事务参与者都要听从协调者的调度, 期间处于阻塞状态, 不能进行其他操作(即可用性问题).

# 三阶段提交

三段式提交针对两阶段提交存在的问题, 引入了一个预询问阶段, 这个阶段事务的协调者将回去询问各个参与者能否执行事务, 参与者根据自身情况返回结果, 而后进行第二阶段预提交, 协调者根据一阶段的询问结果采取相应操作. 询问结果有三种: 1. 所有参与者都返回确定信息. 2. 一个或多个参与者返回否定信息. 3. 协调者等待超时. 针对第一种情况, 协调者会向参与者发送事务执行请求, 参与者收到通知后执行十五但不提交, 然后返回执行情况给协调者. 针对第2.3种情况, 事务协调者会向各参与者发出取消通知. 若在二阶段中事务没有中断, 那么事务协调者会依据各个参与者的事务执行结果决定执行提交或会滚操作.

# Saga

Saga是一个长期运行的事务, 由多个本地事务组成, 每个本地事务有响应的执行模块和补偿模块, 任意一个本地事务出错时可以调用相关事务对应的补偿方法进行补偿, 从而实现最终一致性. 分布式系统中因为网络请求延迟问题, 要求被Saga调用的服务要支持幂等.

Saga适用于业务流程长, 业务流程多. 或参与者包含其他公司或遗留系统的服务, 无法提供TCC三个接口的场景.

Saga是事件驱动架构, 参与者可以异步执行, 吞吐量更高, 并且没有协调者角色存在, 可用性更高. 但Saga不能够保证隔离性.

# Saga的协调模式

# 协同式

Saga的所有节点采用发布/订阅模式进行交互. 这种模式是松耦合的, 采用事务性消息, 在更新领域对象的时候原子性的发布领域事件消息即可. 弊端是难以理解, Saga的逻辑分散在各个服务中. 服务间循环依赖, 并不是好的设计风格. 同时它也存在紧耦合的风险, 如AccountService必须订阅所有可能导致信用卡扣款会退款事件, 因此AccountService内部代码需要与OrderService实现的订单生命周期代码保持同步更新.

# 状态机

实现原理: 1.定义一个编排器类作为状态机角色, 用于通知Saga的参与方应该做什么事情, 这个编排器类采用命令/异步响应模式与其他参与方通信. 当参与方完成操作后, 给编排器回复一个消息, 编排器通过消息决定下一步操作.

状态机模式中没有循环以来, 更简单的依赖关系, 较少的耦合, 每个服务实现供状态机调用的api, 不需要知道Saga参与方发布的事件. Saga的协调逻辑本地化在状态机中, 领域对象更简单.

# Saga的隔离问题.

  • 丢失更新. 一个Saga没有读取更新, 而是直接覆盖另一个Saga的更新.
  • 脏读. 读取到其他尚未完成的Saga的更新.
  • 模糊或不可重复读. 一个Saga中不同步骤读取小童数据获得不同结果, 因为其他Saga进行了更新.

解决方案

  • 语义锁. Saga的可补偿性事务在创建或更新的任何记录中设置一个标志, 用于标示该记录未提交且可能发生更改, 这是一种用于阻止其他事务访问记录的锁.
  • 交换式更新. 把更新操作设计为可以按照任何顺序执行
  • 版本文件. 将更新记录下来, 以便对他们重新排序
  • 悲观视图. 重新排序Saga步骤, 最大限度降低业务风险
  • 重读值. 通过重写数据来防止脏读, 在覆盖数据之前验证是否不变
  • 使用其他分布式事务.
上次更新: 2022/1/6 15:29:54