参考文献

理论

事务

  • 事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态.事务是逻辑上的一组操作,要么都执行,要么都不执行(支持连续SQL的集体成功或集体撤销.);
  • 事务应该具有 4 个属性:原子性、一致性、隔离性、持久性.这四个属性通常称为 ACID 特性.

分布式事务

  • 分布式事务指在分布式系统中,涉及到多个操作的一组事务,这些操作可能分布在不同的计算机上,这些操作必须要保证原子性、一致性、隔离性和持久性,以确保数据的正确性和可靠性.
  • 在分布式系统中,由于存在多个数据节点,因此需要保证数据的一致性.如果在分布式系统中的每个节点都采用独立的事务处理,则可能会导致数据不一致的问题.为了解决这个问题,需要引入分布式事务,以保证多个节点之间的数据操作是一致的.
  • 本质上来说,分布式事务就是为了保证不同数据库的数据一致性.
  • 分布式事务的实现可以采用两种方式:基于两阶段提交协议(2PC)和基于补偿事务的方案.基于2PC的方案是一种经典的实现方式,它通过协调器来实现多个节点之间的事务协调,保证事务的原子性和一致性.而基于补偿事务的方案则是一种更加灵活的方案,它通过在出现异常情况时进行回滚操作,来保证数据的一致性

强一致性、弱一致性、最终一致性

强一致性

  • 强一致性是指在分布式系统中的任何时刻,对于一个数据对象的任何读操作,都能够读取到最近一次写操作的结果.也就是说,无论在分布式系统中的哪个节点上进行读操作,都能够读取到相同的数据值.简言之,在任意时刻,所有节点中的数据是一样的.

  • 强一致性是最严格的一致性模型,通常需要使用同步复制或者分布式锁等机制来实现.由于需要保证实时性和一致性,因此通常会带来较高的性能开销和一定的可用性问题.

弱一致性

  • 弱一致性是指在分布式系统中,不同的节点之间可能存在数据的不一致性,但是这种不一致性是可接受的.即数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性.在弱一致性模型中,读操作可能会读取到旧的数据,或者读操作之间的数据不一致.

  • 弱一致性模型通常用于对实时性要求不高,但是可用性要求较高的场景,例如缓存系统.在缓存系统中,由于缓存数据的更新可能存在一定的延迟,因此可能会导致读操作读取到旧的数据.

最终一致性

  • 最终一致性是指在分布式系统中,不同节点之间的数据可能会存在一定的延迟,但是最终会达到一致的状态.在最终一致性模型中,读操作可能会读取到旧的数据,但是经过一定的时间后,所有节点最终都会达到一致的状态.
  • 即不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化.简单说,就是在一段时间后,节点间的数据会最终达到一致状态.

CAP原则

CAP 原则是指在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)三个特性不可兼得,最多只能同时满足其中两个.

  • CAP 原则的精髓就是要么 AP,要么 CP,要么 AC,但是不存在 CAP.如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时 C 和 P 两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了 CP 系统,但是 CAP 不可同时满足.

一致性Consistency

  • 一致性指的是在分布式系统中,对于任意一个操作,如果有多个副本或者分片,那么在这些副本或者分片上的数据必须是一致的.也就是说,对于任何一个读操作,必须能够读取到最近的一次写操作的结果(等同于所有节点访问同一份最新的数据副本).

可用性Availability

  • 可用性指的是在分布式系统中,无论出现任何故障或者网络分区,系统都能够保证正常的运行,即使出现部分节点或者分片的故障,仍然能够保证系统的正常响应(对数据更新具备高可用性).

分区容错性Partition Tolerance

  • 分区容错性指的是在分布式系统中,无论发生任何网络分区,系统都能够继续运行,保证数据的一致性和可用性.
  • 由于网络分区不可避免,因此在分布式系统中,必须要保证分区容错性.但是在出现网络分区时,必须要选择保证一致性或者可用性中的一个,因为同时保证两者是不可能的.

BASE理论

BASE 理论是对 CAP 原则的一个补充,它是指在分布式系统中,基本可用性(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个特性可以提高系统的可用性和性能.

  • BASE 理论相对于 CAP 原则来说,更加强调系统的可用性和性能,而不是严格的一致性.在实际的分布式系统中,往往需要根据具体的应用场景和需求,选择不同的数据一致性模型和实现方案,以达到最优的性能和可用性.

基本可用性(Basically Available)

  • 基本可用性指的是在分布式系统中,系统必须要保证基本的可用性,即使出现部分故障或者分区,也能够保证系统的正常响应.这与 CAP 原则中的可用性是类似的,但是更加强调系统的基本可用性.

软状态(Soft State)

  • 软状态指的是在分布式系统中,系统的状态可以随着时间的推移而改变,而不必保证实时一致性.这与 CAP 原则中的一致性是类似的,但是更加强调状态的“软性”.
  • 允许系统存在中间状态,而该中间状态不会影响系统整体可用性

最终一致性(Eventually Consistent)

  • 最终一致性指的是在分布式系统中,系统的数据最终会达到一致的状态,但是在过程中可能会存在一定的延迟和不一致.这与 CAP 原则中的一致性是一样的.
  • 实现最终一致性的具体方式
    • 读时修复:在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据。
    • 写时修复:在写入数据,通过写操作的失败错误,发现不一致,然后通过重传修复数据的不
      一致。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败就将数据缓存下来,然后定时重传,修复数据的不一致性。
    • 异步修复:这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。

柔性事务

  • 柔性事务是指在分布式系统中,针对多个操作涉及到的一组事务,采用柔性的方式来保证数据的一致性和可靠性.相比于传统的分布式事务,柔性事务更加灵活和高效.

  • 柔性事务是一种高效、灵活的分布式事务解决方案,它通过提交阶段和确认阶段的方式来保证数据的一致性和可靠性.柔性事务的实现方式较为灵活,可以根据具体的应用场景和需求进行选择.

  • 在柔性事务中,通常将事务分为两个阶段:提交阶段和确认阶段.

    • 提交阶段中,会将所有的操作提交到各自的节点进行执行;
    • 确认阶段中,会对所有节点的执行结果进行确认,如果所有节点都执行成功,则提交事务.如果有任何一个节点执行失败,则回滚事务.
  • 柔性事务的实现可以采用两种方式:基于消息队列的方案基于本地消息表的方案.

    • 基于消息队列的方案通过消息队列来进行异步通信,从而实现分布式事务的提交和回滚操作.
    • 而基于本地消息表的方案则是将消息存储在本地消息表中,通过定时任务来检查和处理未完成的事务.

幂等操作

  • 幂等操作是指对同一操作进行多次执行,结果与执行一次的结果相同.也就是说,多次执行幂等操作,只会产生一次有效的结果,而不会产生多次结果.
  • 在分布式系统中,由于网络等原因,可能会导致同一操作被重复执行多次.如果该操作是幂等的,则不会对系统造成影响,因为多次执行的结果是相同的.但如果该操作不是幂等的,则可能会导致数据的错误或者系统的异常.
  • 常见的幂等操作包括:读取、删除、更新等操作.例如,在数据库中,删除一条记录是幂等操作,无论删除多少次,结果都是相同的.
  • 在实际的应用中,为了保证操作的幂等性,通常需要对操作进行标识或者添加唯一标识符,以确保同一操作只会被执行一次.例如,在支付系统中,为了避免重复支付,可以为每一笔支付添加唯一标识符,以确保同一笔支付只会被执行一次.
  • 总之,幂等操作是分布式系统中常用的操作方式,它可以保证同一操作被重复执行多次时,不会对系统造成影响.为了保证操作的幂等性,通常需要对操作进行标识或者添加唯一标识符.

分布式事务使用场景

高并发场景

  • 在高并发的场景中,可能存在多个用户同时对同一资源进行读写操作,如果不使用分布式事务来保证数据的一致性,可能会导致数据的错误或者冲突.

金融支付场景

  • 在金融支付场景中,由于涉及到资金的流转,必须要保证数据的一致性和可靠性.如果不使用分布式事务来保证支付的原子性和一致性,可能会导致用户的资金损失或者系统的异常.

假设用户 A 使用银行 app 发起一笔跨行转账给用户 B,银行系统首先扣掉用户 A 的钱,然后增加用户 B 账户中的余额.此时就会出现 2 种异常情况:1. 用户 A 的账户扣款成功,用户 B 账户余额增加失败 2. 用户 A 账户扣款失败,用户 B 账户余额增加成功.对于银行系统来说,以上 2 种情况都是不允许发生,此时就需要分布式事务来保证转账操作的成功.

电商订单场景

  • 在电商订单场景中,可能存在多个用户同时下单,而订单数据需要写入多个数据节点.如果不使用分布式事务来保证订单数据的一致性,可能会导致订单数据的错误或者丢失.

分布式存储场景

  • 在分布式存储场景中,可能存在多个节点同时对同一数据进行读写操作,需要保证数据的一致性和可靠性.如果不使用分布式事务来保证数据的一致性,可能会导致数据的错误或者冲突.

分布式事务的解决方案

两阶段提交(Two-Phase Commit,2PC)/XA

  • 其核心思想是通过协调者和参与者之间的交互实现事务的提交和回滚.XA是2PC的一种实现方式,是由X/Open组织制定的规范,定义了分布式事务的接口和行为.

  • XA协议将分布式事务分为两个阶段:

    1. 准备阶段(Prepare Phase):事务管理器向所有本地资源管理器发起请求,询问是否是Ready状态,所有参与者都将本事务能否成功的信息反馈发给协调者;
    2. 提交阶段(Commit Phase):事务管理器根据所有本地资源管理器的反馈,通知所有本地资源管理器,步调一致地在所有分支上提交或者回滚.
  • XA是2PC的一个实现规范,它定义了协调者和参与者之间的接口和协议,保证了不同数据库之间的事务一致性.

    • 存在一个负责协调各个本地资源管理器的事务管理器Transaction Manager(TM),本地资源管理器Resource Manager(RM)一般是由数据库实现
    • 事务管理器TM在第一阶段的时候询问各个资源管理器RM是否都就绪?如果收到每个资源的回复都是YES,则在第二阶段提交事务,如果其中任意一个资源的回复是NO 则回滚事务.
  • XA协议是一个同步的协议,需要等待所有参与者的响应才能进行下一步操作,因此其性能和可靠性都受到一定的影响.

  • 缺点

    • 同步阻塞: 当参与事务者存在占用公共资源的情况,其中一个占用了资源,其他事务参与者就只能阻塞等待资源释放,处于阻塞状态.

    • 单点故障: 一旦事务管理器出现故障,整个系统不可用

    • 数据不一致: 在阶段二,如果事务管理器只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致.

    • 不确定性: 当协事务管理器发送 Commit 之后,并且此时只有一个参与者收到了 Commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功.

补偿事务Try-Confirm-Cancel,TCC

  • 它通过补偿机制实现分布式事务.TCC将事务分解为三个阶段,分别是尝试阶段(Try)、确认阶段(Confirm)和取消阶段(Cancel).
  • TCC 事务机制相比于XA,解决了其几个缺点:
    1. 解决了协调者单点,由主业务方发起并完成这个业务活动.业务活动管理器也变成多点,引入集群.
    2. 同步阻塞:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小.
    3. 数据一致性,有了补偿机制之后,由业务活动管理器控制一致性
  • 具体流程如下:
    • 尝试阶段(Try):在这个阶段中,业务系统会对分布式事务进行资源预留(准隔离性),检查所有参与者是否满足执行事务的条件,如果所有参与者都满足条件(一致性),则事务尝试成功,进入下一阶段;否则,事务尝试失败,进入取消阶段.
    • 确认阶段(Confirm):在这个阶段中,业务系统会执行实际的业务操作,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作满足幂等性.要求具备幂等设计,Confirm 失败后需要进行重试
    • 取消阶段(Cancel):在这个阶段中,业务系统会执行补偿操作,将之前的操作撤销,释放 Try 阶段预留的业务资源 Cancel 操作满足幂等性 Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致.
  • 在 Try 阶段,是对业务系统进行检查及资源预览,比如订单和存储操作,需要检查库存剩余数量是否够用,并进行预留,预留操作的话就是新建一个可用库存数量字段,Try 阶段操作是对这个可用库存数量进行操作.
  • 基于 TCC 实现分布式事务,会将原来只需要一个接口就可以实现的逻辑拆分为 Try、Confirm、Cancel 三个接口,所以代码实现复杂度相对较高.
  • 优点: 它可以灵活地处理分布式事务,支持并行操作,对参与者的状态进行预处理,而且可以通过补偿机制处理各种异常情况.
  • 缺点:在于需要根据具体应用场景设计补偿逻辑,同时需要额外的代码实现.
  • 需要注意的是,在设计TCC方案时,需要考虑以下因素:
    1. 并发冲突:在TCC中,不同的参与者可能会在同一时间对同一资源进行操作,因此需要考虑并发冲突的问题.
    2. 事务恢复:在网络异常等情况下,事务可能会失败,因此需要考虑事务的回滚和恢复.
    3. 性能问题:TCC需要额外的代码实现,可能会对系统性能产生影响

本地消息表(Local Message Table,LMT)

  • 它通过在本地创建消息表,实现分布式事务的处理.LMT的核心思想是在应用程序的本地数据库中创建一个消息表,将事务消息存储在该表中,通过本地的事务来处理消息的发送和确认.通过本地消息表的方式,应用程序可以在本地数据库中保存消息,从而避免了分布式事务的问题,同时还能够保证消息的可靠性.

  • LMT的具体实现方式如下:

    • 发送消息:当应用程序需要发送消息时,首先将消息写入本地消息表中,并开启一个本地事务.
    • 处理消息:应用程序周期性地从本地消息表中读取未处理的消息,并且遍历消息表,对所有未处理的消息进行处理.在处理消息时,应用程序会根据消息的内容,执行相应的业务操作,并将处理结果写入消息表中,同时更新消息的状态为“已处理”.
    • 确认消息:当消息被成功处理后,应该标记该消息为“已处理”,如果处理过程出现异常,应该回滚本地事务,并保持消息的状态为“未处理”.
  • 本地消息表实现的条件:

    1. 消费者与生成者的接口都要支持幂等
    2. 生产者需要额外的创建消息表
    3. 需要提供补偿逻辑,如果消费者业务失败,需要生产者支持回滚操作
  • 需要注意的是,在使用LMT时,需要考虑以下问题:

    • 消息的顺序问题:在多个消息并发处理的情况下,可能会出现消息顺序错误的问题,因此需要考虑如何保证消息的顺序性.
    • 消息的重复问题:在应用程序从消息表中读取消息时,可能会读取到重复的消息,因此需要考虑如何去重.
    • 消息的清理问题:消息表中可能会存在大量过期的消息,需要考虑如何清理过期消息.
  • LMT是一种比较轻量级的分布式事务解决方案,适合于一些需要保证消息可靠性的场景,同时也需要根据具体业务场景进行选择.

可靠消息最终一致性

  • 它通过将消息作为分布式事务的一部分,保证消息的可靠性和最终一致性.在可靠消息最终一致性中,消息发送方会将消息发送到消息队列中,并等待消息队列和所有的接收方确认消息已经被正确处理.如果消息发送方在一定时间内没有收到确认消息,则会重新发送消息,直到消息被正确处理.

  • 大致流程如下

    1. A 系统先向 mq 发送一条 prepare 消息,如果 prepare 消息发送失败,则直接取消操作
    2. 如果消息发送成功,则执行本地事务
    3. 如果本地事务执行成功,则向 mq 发送一条 confirm 消息,如果发送失败,则发送回滚消息
    4. B 系统定期消费 mq 中的 confirm 消息,执行本地事务,并发送 ack 消息.如果 B 系统中的本地事务失败,会一直不断重试,如果是业务失败,会向 A 系统发起回滚请求
    5. mq 会定期轮询所有 prepared 消息调用系统 A 提供的接口查询消息的处理情况,如果该 prepare 消息本地事务处理成功,则重新发送 confirm 消息,否则直接回滚该消息
  • 可靠消息最终一致性的优点在于它可以确保消息的可靠性和最终一致性,同时还能够提高系统的并发性能和可扩展性.缺点在于它需要依赖消息队列来实现,而且需要对消息进行去重和幂等性处理,同时需要考虑消息队列的性能和可靠性问题.

  • 需要注意的是,在使用可靠消息最终一致性时,需要考虑以下问题

    • 消息去重问题:在消息队列中,可能会存在重复消息,需要考虑如何去重.
    • 消息幂等性问题:在消息队列中,可能会出现消息处理失败和重新发送的情况,需要考虑如何保证消息的幂等性.
    • 消息队列的性能和可靠性问题:消息队列作为可靠消息最终一致性的核心组件,需要考虑消息队列的性能和可靠性问题,尤其是在高并发和大规模的场景下,需要考虑如何设计高可用、高性能的消息队列.
  • 与本地消息最大的不同是去掉了本地消息表,其次本地消息表依赖消息表重试写入 mq 这一步由本方案中的轮询 prepare 消息状态来重试或者回滚该消息替代.其实现条件与容错方案基本一致.目前市面上实现该方案的只有阿里的 RocketMq.

尽最大努力通知(Best Effort Delivery,BED)

  • 通过尽最大努力的方式,保证消息的传递,并且不保证消息的可靠性和顺序性.在BED中,消息发送方会将消息发送到消息队列中,并不断尝试重新发送消息,直到消息被正确处理或达到最大重试次数为止.
  • BED的优点在于它能够处理某些不是很关键的消息,对于那些需要高可靠性和顺序性的消息,可以采取其他方案.同时BED还能够提高系统的并发性能和可扩展性.缺点在于它不能保证消息的可靠性和顺序性,同时需要考虑消息队列的性能和可靠性问题.
  • 适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果.
  • 这个方案的大致意思就是:
    1. 系统 A 本地事务执行完之后,发送个消息到 MQ;
    2. 这里会有个专门消费 MQ 的服务,这个服务会消费 MQ 并调用系统 B 的接口;
    3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次,最后还是不行就放弃.

分布式事务实战

两阶段提交/XA

目前支付宝使用两阶段提交思想实现了分布式事务服务 (Distributed Transaction Service, DTS) ,它是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性.

TCC

TCC 需要事务接口提供 try, confirm, cancel 三个接口,提高了编程的复杂性.依赖于业务方来配合提供这样的接口,推行难度大,所以一般不推荐使用这种方式.

可靠消息最终一致性

目前市面上支持该方案的 mq 只有阿里的 rocketmq, 该方案应用场景也比较多,比如用户注册成功后发送邮件、电商系统给用户发送优惠券等需要保证最终一致性的场景

本地消息表

跨行转账可通过该方案实现.

用户 A 向用户 B 发起转账,首先系统会扣掉用户 A 账户中的金额,将该转账消息写入消息表中,如果事务执行失败则转账失败,如果转账成功,系统中会有定时轮询消息表,往 mq 中写入转账消息,失败重试.mq 消息会被实时消费并往用户 B 中账户增加转账金额,执行失败会不断重试.

img

最大努力通知

最大努力通知最常见的场景就是支付回调,支付服务收到第三方服务支付成功通知后,先更新自己库中订单支付状态,然后同步通知订单服务支付成功.如果此次同步通知失败,会通过异步脚步不断重试地调用订单服务的接口

img

img