事务一致性知识盘点

    前面讲了那么多的一致性理论和算法,那么我们必须要了解一下不同一致性算法下的事务、性能、错误的对比关系:

    

    在分布式系统中,比如不同的服务进程操作不同的数据或副本,服务进程间必须要保证事务数据的一致性。所以今天,让我们来回顾一下,事务中的数据一致性。

 

一、事务基础

1.1. 事务的隔离级别

  • 读未提交
  • 读已提交
  • 可重复读
  • 串行化

    而对应的事务并发问题主要就是这些 :

  • 丢失更新-回滚覆盖
  • 脏读:一个事务读到了另一个未提交的数据。
  • 不可重复读:同一事务同一份数据,前后读取结果不一致,也叫“模糊读”。
  • 丢失更新-提交覆盖
  • 幻读:同一事务同一个查询,前后返回结果数不一致。

    它们的关系是这样的:

事务隔离级别回滚覆盖脏读不可重复读提交覆盖幻读
读未提交x可能发生可能发生可能发生可能发生
读已提交xx可能发生可能发生可能发生
可重复读xxxx可能发生
串行化xxxxx

    “不可重复读”和“幻读”很像,但针对点不同。前者针对的是update,关注的是前后读取的结果不一致,即不能读到相同的数据内容;后者针对的是delete或insert,关注的是同样的条件同一对象,前后读取到的条目数不一样。

    从上往下,事务隔离级别越高,并发副作用越小,代价也越大,因为事务隔离实质上是使事务在一定程度上“串行化”,显然与并发是矛盾的。所以我们大多数将数据库DBMS设置为可重复读,来解决了脏读和不可重复读,并具有较好的并发性,或者设置为读已提交比如MySQL,通过MVCC来防止“不可重复读”、增加间隙锁来防止幻读。

 

1.2. 事务的传播性

  • propagation_requierd: 支持当前事务,如果当前没有事务则新建一个事务;
  • propagation_supports: 支持当前事务,如果当前没有事务则以非事务方式执行;
  • propagation_mandatory:支持当前事务,如果当前没有事务则抛出异常;
  • propagation_required_new:不支持当前事务,如果当前存在事务则把当前事务挂起,如果没有事务则新建;
  • propagation_not_supported:不支持当前事务,如果当前存在事务则把当前事务挂起,否则以非事务方式执行;
  • propagation_never:不支持当前事务,如果当前存在事务则抛出异常,否则以非事务方式执行;
  • propagation_nested:如果当前存在事务则在嵌套事务内执行,如果当前没有事务则执行与propagation_required类似的操作 

    Spring默认的事务传播行为是propagation_required,它适合于绝大多数的情况。假设ServiveX#methodX()都工作在事务环境下(即都被Spring事务增强了),程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中。

    

    这七种事务传播性中,“propagation_nested”是Spring提供的一个特殊变量。它要求事务管理器使用JDBC 3.0 Savepoint API提供嵌套事务的行为,即开启一个嵌套事务。对Spring而言,这个内部事务其实是拥有自己的隔离范围和自己的锁。当内部事务开始执行时,它将记录一个保存点(save point),外部事务将被挂起;当内部事务失败,回滚到此保存点;内部事务结束,外部事务唤起。

 

1.3. 事务的一致性

    常见的比如强一致性 ACID和弱一致性 BASE:

    1.3.1. ACID
  • A(原子性)
  • C(一致性):
    • 单调读一致性(访问者:读-读):如果已经读到一个值,那么后续不会读到更早的值。
    • 单调写一致性(副本顺序:写-写):所有的副本以相同的顺序执行所有的写操作。
    • 会话一致性(会话:写-读):提交更新操作的用户在同一个会话里能读取到该数据的最新值。
    • 读写一致性(访问者:写-读):用户对某个数据进行写操作之后,能读取其最新的值,而其他用户不能保证。
    • 因果一致性(多个访问者之间:写-读读):相互独立的A、B、C三个进程对数据进行操作,A进行写后通知B,B进行读,那么能读取到A写之后的数据值,但是由于A没有通知C,那么系统将不保证C一定能够读取到A写之后的数据值。
  • I(隔离性)
  • D(持久性)
    1.3.2. BASE
  • BA(基本可用性):出故障时,允许损失一部分可用性来保证整体可用;
  • S(柔性状态):允许存在中间状态且不影响系统整体可用;
  • E(最终一致性):经过一定延时,最终达到一致的状态;

    BASE其实是为了兼顾可用性、效率的需要,放宽了一致性的要求(并不是完全放弃了ACID),只要“基本可用,最终一致”就可以了。简单来说,“事务完成后的一致性严格遵循,事务中的一致性适当放宽”就是BASE思想。我们把严格遵循ACID、强一致性的事务称为刚性事务,而基于BASE思想实现的事务则称为柔性事务。

    弱一致性和强一致性是相反的,最终一致性其实是弱一致性的特殊情况。

 

 

二、分布式事务

    分布式场景下的事务,常见的难题是:每一个机器节点都能明确的知道自己执行的事务成功 or 失败,但是却无法知道其他分布式节点的事务执行情况。当一个事务要跨越多个分布式节点的时候,为了保证该事务满足事务一致性的要求,一定要引入一个协调者,而其他的节点被称为参与者。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。这就是常见的分布式事务了。

    所以,在大体上,分布式事务都会有一个活动管理器或者中心协调者,负责活动登记和业务编排,并记录日志来做恢复,只是有些是隐式的。比如Saga或本地消息表中的活动发起者。

 

2.1. 刚性事务

    2.1.1. DTP型

    分布式事务有两层:一个是涉及对多个数据库资源的访问,控制在单一服务的内部;一个是涉及对多个服务的访问,是跨越多个服务的。后者很复杂,要解决比如对多资源的协调、事务的跨服务传播等,但本质都是为了保证ACID特性。

    最经典的分布式事务模型就是DTP型了,它包含一个全局事务管理器TM和多个资源管理器RM,以及涉及到的TX协议(描述了APP与TM之间的接口)和XA协议(描述了TM与RM之间的接口)。

    DTP型的实现:

    a)APP向TM申请开始一个全局事务;

    b)APP向TM注册/登记操作的RM,TM通过XA接口通知对应的RM开启子事务;

    c)APP操作RM管理的资源,根据执行情况通知TM提交或回滚全局事务,然后TM通过XA接口告知RM提交或回滚子事务;

        c')先预提交,根据反馈决定正式提交或回滚。

    d)RM操作完毕,全局事务结束;

    基于DTP实现的分布式事务的隔离性,是由每个RM本地事务的隔离性来保证的。在具体实现上,又分为普通DTP和跨域DTP的:

  • 普通DTP:

    

  • 跨域DTP:

    

    DTP型通常实现在数据库资源层,ACID严格,强一致。但DTP型也有很多缺点:

  • 更高的协议成本,效率低下
  • 更脆弱,故障点多
  • 故障影响大,恢复困难

 

    2.1.2. 2PC协议

    2PC,即所谓的“两阶段提交”:

  • prepare阶段:完成资源操作,出现问题则回滚;
  • commit阶段:完成事务提交,不允许出错;

    两阶段事务的关键是在准备阶段,在这个阶段所有参与者必须完成约束检查,达成关于分布式事务一致性的共识。然后在第二阶段,根据之前达成的共识,完成提交或者取消操作。

    提交事务的过程中需要在很多个资源节点之间进行协调,而且每个节点对锁资源的释放必须等到事务最终提交的时候。因此,两阶段事务提交会耗费很长的时间。长时间执行的事务意味着锁资源发生冲突的概率增加。当事务的并发量积累到一定数量的时候,很可能出现事务积压甚至死锁,这将压垮系统的性能和吞吐量。

    2PC的缺点很明显:

  • 对业务有侵入性,多个资源需要提供准备和提交两个接口;
  • 吞吐不高(因为要互斥,事务执行过程中资源都是被锁定的);
  • 存在全局锁问题(造成阻塞与死锁倾向);
  • 高昂的协议成本,导致只适用于短事务,不适合较严苛的分布式场景比如高并发高性能;
  • 牺牲了一定可用性(CAP模型中典型的C+A,不能容忍网络分区);

 

    2.1.3. 3PC协议

    我们假设在一个2PC通信的事务网络中发生机器故障,会有下面几种情况:

  • 协调者挂了,参与者没挂:只需要找到一个新的协调者,来询问所有参与者最后那条事务的执行情况,就可以知道是应该YES or NO了。所以,这种情况不会导致数据不一致;
  • 参与者挂了,协调者没挂:这又可以分为两种情况:
    • 挂了之后没有恢复,不会导致数据一致性的问题;
    • 挂了之后又恢复了,询问协调者应该怎么做,协调者比对自己的事务执行记录和该参与者的事务执行记录,来告诉他怎么做,也能保证数据的一致性了;
  • 参与者挂了,协调者也挂了:这种情况下,新的协调者被选举出来之后,由于不知道参与者在挂之前的执行情况,有可能参与者在挂之前已经执行了事务,但没有人知道他执行了,这就会造成数据不一致了;

    为了避免第三种情况,就有了2PC的改进版本——3PC:

  • CanCommit:询问参与者是否可以执行事务操作,参与者返回YES;
  • PreCommit:执行事务操作,参与者返回YES;
  • DoCommit:执行commit或者rollback;

    简单来说,就是通过将prepare阶段的事务询问和事务执行拆分开,新的协调者不用等其他节点宕机恢复就能决定事务状态。再拿上面第三种情况来说明:当新的协调者发现存活节点中有的节点状态是preCommit或doCommit,那么说明宕机之前所有节点都对"可以提交"达成了共识,协调者就执行commit操作,否则执行rollback操作。而宕机恢复后的参与者通过询问协调者,就能和其他节点保持数据一致性了。

    也就是说,3PC协议解决了2PC协议在参与者、协调者都宕机的情况下事务状态不可知的问题,也降低了全局锁范围。但无论2PC或3PC,均无法彻底解决分布式数据一致性问题,脑裂问题依然存在:参与者收到PreCommit请求后网络分区,等待超时后,不管参与者是继续commit或rollback,因为没有协调者参与协调,始终会有数据不一致的情况。

 

2.2. 柔性事务

    一般来说,服务之间的一致性比服务内部的一致性要更加容易弱化,XA协议、DTP模型在资源层面上实现了通用分布式事务的一致性保证,而当上升到服务层面,服务与服务之间也已实现了功能的划分和逻辑的解耦,也就更容易弱化一致性。也因此,在SOA或微服务架构下更提倡BASE思想的最终一致性,比如TCC模式、Saga模式。即通过业务逻辑将互斥操作从资源层面上移到业务层面,放宽了一致性要求,借助本地事务来实现最终一致性并保证系统的吞吐。

    简单的说,柔性事务就是利用可以利用的业务弹性,业务上对于事务过程中不一致的容忍度,让事务最终一致的方法。比如转账,A账户上的余额扣减、B账户上的余额增加,刚性事务是要求这两个操作同时发生,这在分布式环境下是很难保证的,而柔性事务则允许先减少A账户上的余额,在业务能容忍的时间范围内,最终把B账户的余额给加上去。

 

    2.2.1. 补偿型

    简单而言,补偿型事务的模式上是这样的:

  • do: 执行业务,业务处理结果外部可见;
  • compensate:业务补偿,补偿操作要考虑是否可重入、是否幂等;

    补偿型事务的实现:

  • 主业务服务作为发起者,负责发起并完成整个业务活动;
  • 若干从业务服务作为参与者,提供业务补偿操作;
  • 业务活动管理器作为协调者,负责控制业务活动的一致性,登记业务活动中的操作并在业务活动取消时调用补偿操作;

    

 

    补偿型事务的前提是,业务补偿在业务上是可行的、逻辑上是行得通的,其次是要关注到补偿操作的成本,因有中间态而可能无法完全补偿。

    所以,补偿型的成本在于:

  • 从业务服务实现补偿操作的成本
  • 由于无法完全补偿带来的业务成本
  • 业务活动管理器的业务活动日志的成本(满足可用性)

    补偿型的适用范围:

  • 弱隔离性,弱一致性要求的业务活动
  • 适用于执行时间较长的业务

 

    2.2.2. TCC型

    传统的像2PC协议和数据库的2PL两阶段锁(加锁阶段,只加锁不放锁;解锁阶段,只放锁不加锁),都要求一直持有资源锁直到整个事务执行结束,这导致他们吞吐量都不高、并发性能下降。而TCC型事务,则是通过对业务逻辑的分解,在一阶段结束之后,把资源层的隔离性上升到业务层,放宽了一致性要求,从而释放了整体的并发性能,更多的灵活性。TCC的每个操作对于资源层来说,都是单个本地事务,操作结束则本地事务结束。但这也要求,一阶段要预留业务资源来保证二阶段不会出错,比如交易时一阶段冻结交易资金,到第二阶段时扣除资金。

    TCC型事务的模式:

  • Try操作:尝试执行业务(完成业务检查,预留业务资源);

  • Confirm操作:确认执行业务,不做业务检查,满足幂等性;

  • Cancel操作:取消执行业务(释放预留的业务资源),也要满足幂等性;

    具体的实现是这样的:

  • 主业务服务作为发起者,负责发起并完成整个业务活动;
  • 若干从业务服务作为参与者,提供TCC型业务操作;
  • 业务活动管理器作为协调者,控制业务活动的一致性,登记业务活动中的操作并在业务活动提交/取消时调用相应的Confirm/Cancel操作;

    

    TCC型在事务处理过程中也会有中间态,会有数据短暂不一致的情况,但最终会让事务的数据达到最终一致。

    TCC型除了幂等性要求外,还有两点要求:

  • 允许空回滚:
    在未收到Try请求的情况下收到Cancel请求,这种情况被称为空回滚。这很好理解,比如超时、丢包,此时为了确保事务一致性,将触发Cancel操作。
  • 防悬挂:
    在实现上允许空回滚,但要拒绝执行空回滚后到来的Try请求。

    TCC型的成本:

  • 侵入性,需要用户提供三个接口(Try接口/Confirm接口/Cancel接口);
  • 业务开始时实现Try操作的成本,业务活动结束时Confirm或Cancel操作的成本;

  • 业务活动管理器的业务活动日志的成本;

    TCC型的作用就是协调从业务服务的最终一致性,但它也有缺点,接入成本太高、需要拆分出三个接口。蚂蚁的DTX(对内叫XTS)提供了三种模式的能力:TCC模式、TCC的托管版本——FMT模式和XA模式。

    FMT(Framework-managed transactions)即无侵入的框架托管模式,主要解决的就是TCC型接入成本的问题,提高易用性。它通过一个拦截层,自动解析SQL语义,自动生成二阶段提交和回滚操作。实现上,就是将从业务服务的本地事务作为一阶段,拦截业务SQL,根据SQL保存原数据快照->执行用户SQL->保存新数据快照(类似于MySQL的undo、redo日志);在二阶段,如果事务提交则删除快照,如果事务回滚则校验脏写->合并或覆盖->删除快照。

    FMT型本质上就是TCC型,也包含主业务服务、从业务服务和业务活动管理器,但从业务服务不需要提供Try、Confirm、Cancel三个接口方法,而是按照JDBC标准,通过托管框架与底层数据源交互。也就是说,对业务透明、对业务代码无侵入。

    TCC型的适用范围:

  • 强隔离性,严格一致性要求的业务活动
  • 适用于执行时间较短的业务

 

    2.2.3. Saga型

    Saga型事务也叫“Long-running-transaction”长时间事务,其实就是有一系列本地事务,每个本地事务都有执行和补偿操作,通过发布消息或事件来触发Saga里的下一个本地事务。当Saga中的一个本地事务出错了,通过相关补偿操作做事务恢复,来达到事务的最终一致性。它的核心思想是将长事务拆分成多个本地短事务,由中央协调器协调或相互协调,如果事务失败,则根据调用关系做正向补偿(重试异常步骤)或反向补偿(回滚前序步骤)。

    对应于ACID,Saga只提供了ACD保证:原子性通过Saga协调器来实现,一致性由本地事务和落事务日志来实现,持久性则不用说。也就是说,Saga不保证隔离性,因为Saga事务没有准备阶段,事务没有隔离,而缺少隔离性的后果是,可能会有更新丢失-回滚覆盖、脏读、模糊读等情况。通常我们会在业务层面引入比如业务锁、预先占用资源、Session层隔离等方式来隔离这部分资源,以规避这部分问题。

    对应于2PC、TCC他们是同步的事务模型,而事实上Saga模式就是一种异步补偿事务。

    根据有无中心协调者可以分为:命令协调方式和事件编排方式。

    ♦ 命令/协调方式(Orchestration)

    集中式Saga,就是有一个集中式的中央协调器来协调子事务的执行情况。为了解决分布式Saga事件源难以维护的复杂性问题,引入中央协调器来集中式的进行事件决策和业务编排,,以命令/回复的方式与每个子事务通信,负责告诉每个参与者什么时候该做什么,协调事务的执行 or 回滚。

    集中式Saga的中央协调器协调器,有两点要求:

  • 需要预先知道Saga事务的调用图,一般会通过读取业务流程配置或者XML,或者使用类似Zipkin这样的分布式链路组件,来实现Saga事务调用关系的跟踪;
  • 集中式的协调器,包含Saga调用请求接收、分析、调用执行、结果查询,需要通过流程引擎做业务流的协调;

    集中式Saga的优点:

  • 避免服务之间的循环依赖
  • 只需执行命令或回复,降低参与者的复杂性
  • 在添加新步骤时,事务复杂性保持线性,回滚更容易管理
  • 更容易实施和测试

    集中式Saga的缺点:

  • 存在单点风险
  • 基础设施上,协调器要保证高可用 
  • 参与者和协调器耦合度高

    华为的ServiceComb项目是国内目前唯一的基于集中式Saga实现的开源项目,作为华为云微服务框架引擎的一部分,由ServiceCenter做服务发现(基于ETCD)、由Zipkin做链路追踪。它有两个参与者,一边是协调端,负责协调事务执行情况,一边是监控端,负责收集事务执行情况,并向协调端上报信息并执行相关指令。

    大致的实现方式:

  • 监控端通过注解或者XML配置以切面编程的方式向应用注入相关的模块,比如拦截事务请求,用来构建分布式事务调用的上下文;
  • 监控端在事务初始阶段处理事务的资源准备操作,例如创建Saga起始事件以及相关的子起始事件,根据事务执行的成功或者失败生产相关的事务终止或者失败事件;
  • 协调端收到事件通知后,根据Saga事务执行情况,来决定是否给客户端下达指令进行相关的回滚恢复;
  • 如果服务调用的过程中抛出异常,监控端将终止事件发送到协调器,协调端进程会定时做扫描,发现有需要恢复的事件,而后发消息通知客户端调用相关的恢复操作,来保证整个Saga事务的原子性;

    ♦ 事件/编排方式(Choreography)

    分布式Saga,就是用事件驱动的方式让参与的服务相互交互(订阅相关的领域事件),服务方自己彼此协调。就是说,每个服务生产并监听其他服务的事件,并决定是否应采取行动。第一个服务执行一个事务,然后发布一个事件,该事件被一个或多个服务监听,这些服务再执行本地事务并发布(或不发布)新的事件;当最后一个服务执行完本地事务且不发布任何事件时,意味着事务结束。
    比如订单服务,发布一个订单创建事件,被支付服务监听,进行完支付并发布支付完成事件后,被库存服务监听,做库存扣减并发布库存准备事件后,被货运服务监听,下货运单并发布货物交付事件,最后再被订单服务监听,变更订单状态为完成。因为需要跟踪订单状态,订单服务可能需要监听所有事件并更新其状态,其实除了订单服务以外的其他服务都是订单服务的子服务。也就是说,一个订单的完成需要经过这么些步骤,领域上订单服务与这些服务是包含与被包含关系,订单服务即发起方在业务上就是一个协调者。

    分布式Saga的优点:

  • 简单,容易理解
  • 降低系统的复杂度,提高系统扩展性
  • 所有参与者都是松耦合的
  • 如果事务涉及步骤不是很多,那么它是合适的

    分布式Saga的缺点:

  • 如果涉及大量的业务事件会变得很混乱,即难以维护难以跟踪比如哪些服务监听哪些事件
  • 服务之间会有循环依赖的问题,因为它们必须订阅彼此的事件
  • 流程测试实施起来很麻烦

 

    2.2.4. 消息型

    消息型事务,即通过将同步的事务操作转变为基于MQ、消息队列的异步操作,避免了对数据库事务的争用,以及同步阻塞操作的影响,它其实就是通过本地事务+消息表的定时补偿来实现分布式事务。消息型事务的约束,一般来说,业务参与方的事务处理结果不影响业务发起方的处理结果

    在具体实现上,又分为两个版本:

    ♦ 最大努力型

    实现方式:

  • 业务发起方,在完成业务处理的同一个本地事务中,记录消息数据;
  • 事务提交后,发送消息通知业务参与方,发送成功即删除/失效消息数据;
  • 业务发起方定期扫描未成功发送的消息,做消息补发;

    最大努力型模式,就是通过可靠消息最大限度的通知参与方(以最大重试次数为上限),利用MQ或消息队列可靠消息的特性来保证分布式事务处理的一种方式。它的成本,在于发起方本地消息表的建设成本,以及业务系统对消息数据CRUD成本。

    它的适用范围:

  • 对业务最终一致性的时间敏感度高;
  • 降低业务参与方的实现成本;

    ♦ 数据核对型

    实现方式:

  • 业务发起方完成业务事务后,发送消息通知业务参与方;
  • 业务参与方定期向业务发起方询问,恢复可能丢失的消息数据;

    

        简单来说就是定期校对、核查,数据核对型模式的成本,是业务查询与校对系统的建设成本。它的适用范围,主要是对业务最终一致性的时间敏感度低的事务。

    ♦ 异步确保型

    实现方式:

  • 业务发起方在事务提交前,向消息中心发送消息数据,消息中心只记录、不发送(即事务型消息);
  • 事务提交后,业务发起方向消息中心确认发送,消息中心收到确认后才真正开始发送消息数据;
  • 事务回滚后,业务发起方向消息中心取消发送,消息中心删除/失效消息数据;
  • 消息中心定期扫描未确认发送或回滚发送的消息数据,询问业务发起方的消息状态,后者根据消息ID或消息数据内容属性确定该消息是否有效,以决定是否发送该消息数据;

    

    异步确保型模式,其实是利用了MQ或消息队列的事务型消息,MQ的异步回查来确保本地消息表未确认或回滚发送的消息补偿。它的成本,在于一次消息发送需要两次请求的通信成本,以及业务发起方需提供消息状态回查接口的业务代码侵入成本。但它的优点是,消息数据独立存储、独立伸缩,降低了业务系统与消息系统间的耦合。

    

    消息型事务可以是单向的也可以是双向的,即发起方可以只管消息成功投递给参与方,对参与方成功或失败不关心,也可以关心后者的消费结果。它与分布式Saga的区别在于,分布式Saga的发起方天然就是一个隐式的协调者,它需要感知消息链路上每个节点上的状态变更,而双向的消息型事务,更多的是两个或三个节点互通。A通知BB通知C,B的成功失败、C的成功失败消费发送方可以感知,但在分布式Saga中A必须要感知。

 

    2.2.5. “无锁”型

    在本地事务上,事务性能和吞吐瓶颈往往是因为强事务带来的资源锁,如何很好地解决资源锁问题往往是实现事务高性能的关键。放弃资源锁也是解决问题的一个思路,但放弃锁并不意味着放弃隔离性,如果隔离性没保障,则必然要在后续引入其它方案来规避可能会大量出现的数据脏读、模糊读等问题。

    无锁型的实现方式:

  • 避免事务进入回滚:
    事务出现异常时,如果可以不回滚也能满足业务要求,也就是要求业务不管出现任何情况,只能继续朝事务处理流程的正向继续处理,这样中间状态即使对外可见,由于事务不会回滚,也不会有什么脏读的问题。
  • 辅助业务变化明细表:
    对热数据数据源(比如资金或商品库存)进行事务操作时,采用记录这些增减变化的明细表或日志记录的方式,避免大量事务均对同一数据表进行更新操作,从而造成数据访问热点的问题。
    同时,不同事务中处理的数据进行业务维度的拆分和隔离,互不干扰互不影响,拉低事务并发问题的发生概率。
  • 乐观锁
    悲观的锁策略对数据访问具有极强的排他性,也是产生事务处理瓶颈的原因之一。若能用乐观锁(比如状态机)、自旋,则在一定程度上缓解了这个问题。

 

2.3. 总结

    在实际应用中,大型分布式事务的解决方案,一般是对一个大的事务进行拆分,拆成若干个小的事务,然后通过设计业务补偿等手段来考虑最终一致性。即大多数的分布式场景,尽量不用强一致性事务(除非有强一致性需求),更脆弱、效率更低,最简单的办法就是允许不一致,通过本地事务(XA)+MQ可靠消息/异步确保/定期补偿,提供回滚接口来达到最终一致性。

    简单的实现过程:

  • 事务提交:
  • 事务回滚:
  • 事务恢复:

    实现策略:

  • 在主控动作的本地事务提交/回滚后,由业务活动管理器接管执行;
  • 通过注册事务同步器监听主控动作的本地事务提交事件;
  • 提交过程可能时间较长,具体实现时,尽量让这个过程异步化、并行化;
  • 提交过程中如果发生故障造成处理中断,由恢复过程自行恢复;

    实践上的总结:

  1. 事务ID的生成规则:
    需要为每个事务创建一个唯一的事务ID,便于后续追踪处理步骤,以及幂等性需求;
  2. 可靠投递与回查:
    在事件或命令中添加回复地址,便于确定来源和链路追踪;
    设计上要有一定的容错能力,比如因为上游的bug,可能会触发/重发不需要的消息;
  3. 业务补偿要求:
    幂等性,空回滚与防悬挂
    可以定期做数据核对比如T+1,降低业务风险
  4. 并发控制
    预留资源或锁资源
    发生竞争之后的业务补偿或异步重试
    大事务拆多个小事务,降低并发度
    做异步化,降低锁时长
    通过蓄洪、削峰填谷,降低数据热点

 

    最后,来张知识体系导图作为总结:

    

※ 附录:

参考:

转载于:https://my.oschina.net/duofuge/blog/3013361

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值