《深入理解分布式事务:原理与实战》读书笔记

Mysql事务

锁升级

行锁升级表锁:如果不是索引查找,或者索引失效,此时需要全表扫描,会升级为锁整张表。
为什么Mysql要把扫描到的每一行以及其间隙都加锁?这是为了防止幻读出现。幻读导致的问题是破坏了一致性声明,并且导致binlog混乱

mvcc

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
更新和删除不会改变旧版本数据,而是将旧版本数据复制到undoLog中。

redoLog

在这里插入图片描述

undolog

事务回滚 、mvcc
从本质上说,为实现MVCC机制,InnoDB存储引擎在数据库每⾏数据的后⾯添加了3个字段:6字节的事务id(DB_TRX_ID)字段、7字节的回滚指针(DB_ROLL_PTR)字段、6字节的DB_ROW_ID字段
6字节的事务id(DB_TRX_ID)字段。
⽤来标识最近⼀次对本⾏记录做修改(insert、update)的事务的标识符,即最后⼀次修改本⾏记录的事务id。如果是delete操作,在InnoDB存储引擎内部也属于⼀次update操作,即更新⾏中的⼀个特殊位,将⾏标识为已删除,并⾮真正删除。
2)7字节的回滚指针(DB_ROLL_PTR)字段。
主要指向上⼀个版本的⾏记录,能够从最新版本的⾏记录逐级向上,找到要查找的⾏版本记录。
3)6字节的DB_ROW_ID字段。
这个字段包含⼀个随着新数据⾏的插⼊操作⽽单调递增的⾏id,当由InnoDB存储引擎⾃动产⽣聚集索引时,聚集索引会包含这个⾏id,否则这个⾏id不会出现在任何索引中。

binlog

binlog记录模式:Row和Statement
1.Row模式
Row模式下的BinLog⽂件会记录每⼀⾏数据被修改的情况,然后在MySQL从数据库中对相同的数据进⾏修改。

Row模式的优点是能够⾮常清楚地记录每⼀⾏数据的修改情况,完全实现主从数据库的同步和数据的恢复。

Row模式的缺点是如果主数据库中发⽣批量操作,尤其是⼤批量的操作,会产⽣⼤量的⼆进制⽇志。⽐如,使⽤alter table操作修改拥有⼤量数据的数据表结构时,会使⼆进制⽇志的内容暴涨,产⽣⼤量的⼆进制⽇志,从⽽⼤⼤影响主从数据库的同步性能。
2.Statement模式
Statement模式下的BinLog⽂件会记录每⼀条修改数据的SQL语句,MySQL从数据库在复制SQL语句的时候,会通过SQL进程将BinLog中的SQL语句解析成和MySQL主数据库上执⾏过的SQL语句相同的SQL语句,然后在从数据库上执⾏SQL进程解析出来的SQL语句。

Statement模式的优点是由于不记录数据的修改细节,只是记录数据表结构和数据变更的SQL语句,因此产⽣的⼆进制⽇志数据量⽐较⼩,这样能够减少磁盘的I/O操作,提升数据存储和恢复的效率。

Statement模式的缺点是在某些情况下,可能会导致主从数据库中的数据不⼀致。例如,在MySQL主数据库中使⽤了last_insert_id()和now()等函数(主从执行sql返回的结果不一致),会导致MySQL主从数据库中的数据不⼀致。

binlog写入
事务在Commit阶段会将产⽣的⽇志事件写⼊磁盘的BinLog⽂件中。因为不同的事务会以串⾏的⽅式将⽇志事件写⼊BinLog⽂件中,所以⼀个事务中包含的⽇志事件信息在BinLog⽂件中是连续的,中间不会插⼊其他事务的⽇志事件

两阶段提交
之所以要先redo prepare后bin,是由于binlog不具备主动的崩溃恢复能力。
在这里插入图片描述
BinLog组提交机制
在不开启binlog的场景下,redolog可以采用组提交,多个事务的redologbuffer都写完后,再进行统一的fsync。但是开启binlog后,由于要进行 二阶段提交,而redolog的prepare操作会上一把锁:prepare_commit_mutex,这把锁导致多个事务无法同时prepare,从而导致无法组提交redolog。
加锁前:prepare和write的顺序不一样,导致不一致问题
在这里插入图片描述
加锁后:
在这里插入图片描述

这个问题在MySQL 5.6中得到了解决。在MySQL 5.6中,提交事务时会在InnoDB存储引擎的上层将事务按照⼀定的顺序放⼊⼀个队列,队列中的第⼀个事务称为leader,其他事务称为follower。在执⾏顺序上,虽然还是先写BinLog,再写事务⽇志,但是写⽇志的机制发⽣了变化:移除了prepare_commit_mutex锁。开启BinLog后,组提交功能不会失效。BinLog的写⼊和redoLog写⼊都是通过组提交功能进⾏的。这种实现⽅式称为⼆进制⽇志组提交(Binary Log Group Commit,BLGC)。
BLGC的实现主要分为Flush、Sync和Commit三个阶段。

1)Flush阶段:将每个事务的BinLog写⼊对应的内存缓冲区。
2)Sync阶段:将内存缓冲区中的BinLog写⼊磁盘的BinLog⽂件,如果队列中存在多个事务,则此时只执⾏⼀次刷盘操作就可以将多个事务的BinLog刷新到磁盘的BinLog⽂件中,这就是BLGC操作。
3)Commit阶段:leader事务根据队列中事务的顺序调⽤存储引擎层事务的提交操作,由于InnoDB存储引擎本身就⽀持组提交功能,因此解决了prepare_commit_mutex锁导致的组提交功能失效的问题。

在Flush阶段,将BinLog写⼊内存缓冲区时,不是写完就⽴刻进⼊Sync阶段,⽽是等待⼀定时间,多积累⼏个事务的BinLog再⼀起进⼊Sync阶段

这里有个问题,这种BLGC的方式我读下来本质上是先一块写binlog,再一块写redolog,那会不会导致写redo的时候失败,然后binlog和redolog不一致呢?
https://zhuanlan.zhihu.com/p/143052123?utm_id=0
其实是先一块写prepare,然后再一块写binlog(以prepare的顺序),最后再commit。

XA事务

在这里插入图片描述
1)事务管理器:主要对参与全局事务的各个分⽀事务进⾏协调,并与资源管理器进⾏通信。
2)资源管理器:主要提供对对事务资源的访问能⼒。实际上,⼀个数据库就可以看作⼀个资源管理器。
3)应⽤程序:主要⽤来明确全局事务和各个分⽀事务,指定全局事务中的各个操作

因为XA事务是基于两阶段提交的分布式事务,所以XA事务也被拆分为Prepare阶段和Commit阶段。

在Prepare阶段,事务管理器向资源管理器发送准备指令,资源管理器接收到指令后,执⾏数据的修改操作并记录相关的⽇志信息,然后向事务管理器返回可以提交或者不可以提交的结果信息。

在Commit阶段,事务管理器接收所有资源管理器返回的结果信息,如果某⼀个或多个资源管理器向事务管理器返回的结果信息为不可以提交,或者超时,则事务管理器向所有的资源管理器发送回滚指令。如果事务管理器收到的所有资源管理器返回的结果信息为可以提交,则事务管理器向所有的资源管理器发送提交事务的指令。

JDBC使用XA:

import com.mysql.jdbc.jdbc2.optional.MysqlXAConnection;
import com.mysql.jdbc.jdbc2.optional.MysqlXid;
import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class MysqlXAConnectionTest {
    public static void main(String[] args) throws SQLException {
//打印XA⽇志
        boolean writeLog = true;
// 获得资源管理器操作接⼝实例RM1
        Connection conn1 = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "binghe", "binghe123");
//配置打印XA⽇志
        XAConnection xaConn1 = new MysqlXAConnection((com.mysql.jdbc.Connection) conn1, writeLog);
        XAResource rm1 = xaConn1.getXAResource();
// 获得资源管理器操作接⼝实例RM2
        Connection conn2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "binghe","binghe123");
//配置打印XA⽇志
        XAConnection xaConn2 = new MysqlXAConnection((com.mysql.jdbc.Connection) conn2, writeLog);
        XAResource rm2 = xaConn2.getXAResource();
// 应⽤程序请求事务管理器执⾏⼀个分布式事务,事务管理器⽣成全局事务id
        byte[] gtrid = "binghe123".getBytes();
        int formatId = 1;
        Xid xid1=null;
        Xid xid2=null;
        try {
// ==============分别执⾏RM1和RM2上的事务分⽀====================
// 事务管理器⽣成rm1上的事务分⽀id
            byte[] bqual1 = "binghe001".getBytes();
            xid1 = new MysqlXid(gtrid, bqual1, formatId);
// 执⾏rm1上的事务分⽀
            rm1.start(xid1, XAResource.TMNOFLAGS);
            PreparedStatement ps1 = conn1.prepareStatement("INSERT into xa_test(name) VALUES ('binghe')");
            ps1.execute();
            rm1.end(xid1, XAResource.TMSUCCESS);
// 事务管理器⽣成rm2上的事务分⽀id
            byte[] bqual2 = "binghe002".getBytes();
            xid2 = new MysqlXid(gtrid, bqual2, formatId);
// 执⾏rm2上的事务分⽀
            rm2.start(xid2, XAResource.TMNOFLAGS);
            PreparedStatement ps2 = conn2.prepareStatement("INSERT into xa_test(name) VALUES ('binghe')");
            ps2.execute();
            rm2.end(xid2, XAResource.TMSUCCESS);
// ===================两阶段提交================================
// 第⼀阶段:通知所有的资源管理器准备提交事务分⽀
            int rm1_prepare = rm1.prepare(xid1);
            int rm2_prepare = rm2.prepare(xid2);
// 第⼆阶段:提交所有事务分⽀
            boolean onePhase = false;
//所有事务分⽀都进⼊准备状态,提交所有事务分⽀
            if (rm1_prepare == XAResource.XA_OK
                    && rm2_prepare == XAResource.XA_OK ) {
                rm1.commit(xid1, onePhase);
                rm2.commit(xid2, onePhase);
            } else { //如果有事务分⽀没有进⼊准备状态,则回滚所有的分⽀事务
                rm1.rollback(xid1);
                rm2.rollback(xid2);
            }
        } catch (XAException e) {
// 如果出现异常,也要进⾏回滚
            rm1.rollback(xid1);
            rm2.rollback(xid2);
            e.printStackTrace();
        }
    }
}

Spring事务

从本质上讲,Spring事务是对数据库事务的进⼀步封装。也就是说,如果数据库不⽀持事
务,Spring也⽆法实现事务操作。Spring并不是直接管理事务的,⽽是提供了多种事务管理器。通过这
些事务管理器,Spring将事务管理的职责委托给了Hibernate、MyBatis、JTA等持久化框架的事务来实现。

事务传播机制

本质上是,新的方法操作,面对当前是否存在事务的不同表现。如果当前没有事务,是开启一个事务(REQUIRED)?还是以非事务的方式执行(SUPPORT)?还是抛出异常(MANDATORY)?如果当前有事务,是加入这个事务,与其组成一个整体(REQUIRED)?还是挂起当前事务,并新建一个事务执行(REQUIRES_NEW)?还是嵌套这个事务(NESTED)?还是抛出异常(NEVER)?还是挂起当前事务,以非事务的方式执行(NOT_SUPPORTED)?

使用场景
REQUIREDSpring中默认的传播机制,适⽤于⼤部分场景

NOT_SUPPORTED适⽤于发送提示信息、站内信、短信、邮件等,这类场景要求不影响系统
的主体业务逻辑,即使操作失败也不应该对主体逻辑产⽣影响,不能使主体逻辑的事务回滚

REQUIRES_NEW总是创建新的事务执⾏,适⽤于不受外层⽅法事务影响的场景。例如记录⽇
志的操作,不管主体业务逻辑是否已经完成,⽇志都要记录下来,不能因为主体业务逻辑异常
事务回滚⽽导致⽇志操作冋滚

注意:如果在同一个service类中定义的两个方法, 内层REQUIRES_NEW并不会开启新的事务,save和update中回滚都会导致整个事务的回滚
如果在不同的service中定义的两个方法, 内层REQUIRES_NEW会开启新的事务,并且二者独立,事务回滚互不影响

事务失效:如果同⼀个类中的两个⽅法A和B上均添加了事务注解,⽅法A调⽤⽅法B,则⽅法B的事务会失效;如果异常被捕获,则也不会触发事务回滚;Spring中默认回滚的事务异常类型为RuntimeException,如果不是,则不会触发事务回滚,可以在注解上定义目标异常类型

分布式事务的基本概念

架构演进

在这里插入图片描述
这种架构的优点如下。
1)架构简单,项⽬开发和维护成本低。
2)所有项⽬模块部署在⼀起,对于⼩型项⽬来说,⽅便维护。
但是,其缺点也是⽐较明显的。
1)所有模块耦合在⼀起,对于⼤型项⽬来说,不易开发和维护。
2)项⽬各模块之间过于耦合,⼀旦有模块出现问题,整个项⽬将不可⽤。
3)⽆法针对某个具体模块来提升性能。
4)⽆法对项⽬进⾏⽔平扩展。

正是由于单体应⽤架构存在诸多缺点,才逐渐演变为垂直应⽤架构。

在这里插入图片描述
这种架构的优点如下。
1)对系统进⾏拆分,可根据不同系统的访问情况,有针对性地进⾏优化。
2)能够实现应⽤的⽔平扩展。
3)各系统能够分担整体访问流量,解决了并发问题。
4)⼦系统发⽣故障,不影响其他⼦系统的运⾏情况,提⾼了整体的容错率。
这种架构的缺点如下。
1)拆分后的各系统之间相对独⽴,⽆法进⾏互相调⽤。
2)各系统难免存在重叠的业务,会存在重复开发的业务,后期维护⽐较困难

将系统演变为垂直应⽤架构之后,当垂直应⽤越来越多时,重复编写的业务代码就会越来越
多。此时,我们需要将重复的代码抽象出来,形成统⼀的服务,供其他系统或者业务模块调
⽤,这就是分布式架构
在这里插入图片描述
这种架构的优点如下。
1)将重复的业务代码抽象出来,形成公共的访问服务,提⾼了代码的复⽤性。
2)可以有针对性地对系统和服务进⾏性能优化,以提升整体的访问性能。
这种架构的缺点如下。
1)系统之间的调⽤关系变得复杂。
2)系统之间的依赖关系变得复杂。
3)系统维护成本⾼。

在分布式架构下,当部署的服务越来越多时,重复的代码就会变得越来越多,不利于代码的复
⽤和系统维护。为此,我们需要增加⼀个统⼀的调度中⼼对集群进⾏实时管理,这就是
SOA(⾯向服务)架构。
在这里插入图片描述
这种架构的优点是通过注册中⼼解决了各个服务之间服务依赖和调⽤关系的⾃动注册与发现。
这种架构的缺点如下。
1)各服务之间存在依赖关系,如果某个服务出现故障,可能会造成服务器崩溃。
2)服务之间的依赖与调⽤关系复杂,增加了测试和运维的成本

微服务架构是在SOA架构的基础上进⾏进⼀步的扩展和拆分。在微服务架构下,⼀个⼤的项⽬
拆分为⼀个个⼩的可独⽴部署的微服务,每个微服务都有⾃⼰的数据库
在这里插入图片描述
这种架构的缺点如下。
1)开发成本⽐较⾼。
2)涉及各服务的容错性问题。
3)涉及数据的⼀致性问题。
4)涉及分布式事务问题

分布式事务场景

将⼀个⼤的应⽤系统拆分为多个可以独⽴部署的应⽤服务,需要各个服务远程协作才能完成某
些事务操作,这就涉及分布式事务的问题。总的来讲,分布式事务会在3种场景下产⽣,分别
是跨JVM进程、跨数据库实例和多服务访问单数据库。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

数据一致性问题

总的来说,数据的⼀致性问题包含数据多副本、调⽤超时、缓存与数据库不⼀致、多个缓存节
点数据不⼀致等场景。

  1. 数据多副本场景
    如果数据的存储存在多副本的情况,当⽹络、服务器或者系统软件出现故障时,可能会导致⼀
    部分副本写⼊成功,⼀部分副本写⼊失败,造成各个副本之间数据的不⼀致。
  2. 调⽤超时场景
    调⽤超时场景包含同步调⽤超时和异步调⽤超时。
    同步调⽤超时往往是由于⽹络、服务器或者系统软件异常引起的,例如,服务A同步调⽤服务B
    时出现超时现象,导致服务A与服务B之间的数据不⼀致。
    异步调⽤超时是指服务A异步调⽤服务B,同样是由于⽹络、服务器或者系统软件异常导致调⽤
    失败,出现服务A与服务B之间的数据不⼀致的情况。⼀个典型的场景就是⽀付成功的异步回调
    通知。
  3. 缓存与数据库不⼀致场景
    这种场景主要针对缓存与数据库。在⾼并发场景下,⼀些热数据会缓存到Redis或者其他缓存
    组件中。此时,如果对数据库中的数据进⾏新增、修改和删除操作,缓存中的数据如果得不到
    及时更新,就会导致缓存与数据库中数据不⼀致。
  4. 多个缓存节点数据不⼀致场景
    这种场景主要针对缓存内部各节点之间数据的不⼀致。例如在Redis集群中,由于⽹络异常等
    原因引起的脑裂问题,就会导致多个缓存节点数据不⼀致。

针对数据不一致的解决方案:ACID特性、CAP理论、Base理论、DTP模型、2PC(两阶段提交)模型、3PC(三阶段提交)模型、TCC模型、可靠消息最终⼀致性模型、最⼤努⼒通知模型等。

分布式事务理论知识

CAP

C:
1)存在数据同步的过程,应⽤程序的写操作存在⼀定的延迟。
2)为了保证各节点数据的⼀致性,需要对相应的资源进⾏锁定,待数据同步完成后再释放锁
定的资源。
3)如果数据写⼊并同步成功,所有节点都会返回最新的数据。相反地,如果数据写⼊或者同
步失败,所有节点都不会存在最新写⼊的数据。
A:
1)所有的请求都会被响应。
2)不会存在响应超时或者响应错误的情况。
3)如果对不同的应⽤程序设定了超时响应时间,⼀旦超过这个时间,系统将不可⽤。
P:
1)⼀个节点挂掉,不影响其他节点对外提供服务。
2)分区容忍性是分布式系统必须具备的基础能⼒。

BASE

Base理论是对CAP理论中AP的⼀个扩展,它通过牺牲强⼀致性来获得可⽤性。Base理论中的
Base是基本可⽤(Basically Available)、软状态(Soft State)和最终⼀致性
(Eventually Consistent)的缩写。当系统出现故障时,Base理论允许部分数据不可⽤,但是
会保证核⼼功能可⽤;允许数据在⼀段时间内不⼀致,但是经过⼀段时间,数据最终是⼀致
的。

BA:
基本可⽤是指分布式系统出现故障时,允许其损失系统的部分可⽤性,⽐如响应时间或者功能
上的损失,但是要保证系统基本可⽤。例如在电商业务场景中,添加购物⻋和下单功能出现故
障时,商品浏览功能仍然可⽤。
S:
软状态是指允许系统中存在中间状态,这些中间状态不会影响系统的整体可⽤性,只是允许系
统各个节点之间的数据同步存在延迟。例如在电商业务场景中,订单中的“⽀付中”“退款中”等状
态就是中间状态,当达到⼀段时间后,就会变成“⽀付成功”或者“退款成功”的状态。
E:
最终⼀致性是指系统中各个节点的数据副本经过⼀段时间的同步,最终能够达到⼀致的状态。
最终⼀致性需要保证数据经过⼀段时间的同步达到⼀致,并不要求各个节点的数据保持实时⼀
致。例如在电商业务场景中,订单中的“⽀付中”“退款中”等状态,最终会变成“⽀付成功”“退款成
功”的状态,经过⼀段时间的延迟,能够使得订单中的状态与最终的交易结果⼀致。

强一致性分布式事务解决方案

在分布式事务领域,最早采⽤的是符合CAP理论的强⼀致性事务⽅案来解决分布式事务问题。
强⼀致性分布式事务要求在任意时刻查询参与全局事务的各节点的数据都是⼀致的

强⼀致性事务解决⽅案存在如下优点。
1)数据⼀致性⽐较⾼。
2)在任意时刻都能够查询到最新写⼊的数据。
强⼀致性事务解决⽅案也存在着如下缺点。
1)存在性能问题,在分布式事务未完全提交和回滚之前,应⽤程序不会查询到最新的数据。
2)实现复杂。
3)牺牲了可⽤性。
4)不适合⾼并发场景

在强⼀致性事务解决⽅案中,典型的⽅案包括DTP模型(全局事务模型)、2PC模型(⼆阶段
提交模型)和3PC模型(三阶段提交模型)3种。

DTP模型

1)事务:⼀个事务就是⼀个完整的⼯作单元,具备ACID特性。
2)全局事务:由事务管理器管理的事务,能够⼀次性操作多个资源管理器。
3)分⽀事务:由事务管理器管理的全局事务中,每个资源管理器中独⽴执⾏的事务。
4)控制线程:执⾏全局事务的线程,这个线程⽤来关联应⽤程序、事务管理器和资源管理器
三者之间的关系,也就是表示全局事务和分⽀事务的关系,通常称为事务上下⽂环境。

在这里插入图片描述
1)AP:应⽤程序(Application Program)可以理解为参与DTP分布式事务模型的应⽤程序。
2)RM:资源管理器(Resource Manager)可以理解为数据库管理系统或消息服务管理器。
应⽤程序可以通过资源管理器对相应的资源进⾏有效的控制。相应的资源需要实现XA定义的接
⼝。
3)TM:事务管理器(Transaction Manager)负责协调和管理DTP模型中的事务,为应⽤程序
提供编程接⼝,同时管理资源管理器。

2PC模型

1.Prepare阶段
在Prepare阶段,事务管理器给每个参与全局事务的资源管理器发送Prepare消息,资源管理器
要么返回失败,要么在本地执⾏相应的事务,将事务写⼊本地的Redo Log⽂件和Undo Log⽂
件,此时,事务并没有提交。
2.Commit阶段
如果事务管理器收到了参与全局事务的资源管理器返回的失败消息,则直接给Prepare阶段执
⾏成功的资源管理器发送回滚消息,否则,向每个资源管理器发送Commit消息。相应的资源管
理器根据事务管理器发送过来的消息指令,执⾏对应的事务回滚或事务提交操作,并且释放事
务处理过程中使⽤的锁资源。

值得注意的是,2PC模型存在着如下的缺点。
1)同步阻塞问题:事务的执⾏过程中,所有参与事务的节点都会对其占⽤的公共资源加锁,
导致其他访问公共资源的进程或者线程阻塞。
2)单点故障问题:如果事务管理器发⽣故障,则资源管理器会⼀直阻塞。
3)数据不⼀致问题:如果在Commit阶段,由于⽹络或者部分资源管理器发⽣故障,导致部分
资源管理器没有接收到事务管理器发送过来的Commit消息,会引起数据不⼀致的问题。
4)⽆法解决的问题:如果在Commit阶段,事务管理器发出Commit消息后宕机,并且唯⼀接收
到这条Commit消息的资源管理器也宕机了,则⽆法确认事务是否已经提交。

在这里插入图片描述
在这里插入图片描述

3PC

3PC模型是指三阶段提交模型,是在2PC模型的基础上改进的版本。3PC模型把2PC模型中的
Prepare阶段⼀分为⼆,最终形成3个阶段:CanCommit阶段、PreCommit阶段和doCommit或
者doRollback阶段。

其中CanCommit只是询问,不涉及undolog和redolog的写入。资源管理器收到CanCommit消息,认为能够执⾏事务,会向事务管理器响应Yes消息,进⼊预备状态。资源管理器收到PreCommit消息后,执⾏事务操作,将Undo和Redo信息写⼊事务⽇志,并向事务管理器响应Ack状态,但此时不会提交事务。事务管理器接收到doCommit消息后,正式提交事务,并释放执⾏事务期间占⽤的资源,同时向事务管理器响应事务已提交的状态
对比
3PC比2PC多了一个cancommit阶段,减少了不必要的资源浪费。
因为2PC的第一阶段直接就执行事务操作并将事务信息写入日志了,要是后面失败就浪费了资源;而3PC的cancommit阶段可以校验是否可以执行,如果不能执行就直接返回,这样就减少了资源的浪费。

3PC引入超时机制,同时在协调者和参与者中都引入超时机制。
2PC:只有协调者有超时机制,超时后才发送回滚指令。
3PC:协调者和参与者都有超时机制
协调者超时:cancommit、precommit中,如果收不到参与者的反馈,则协调者向参与者发送终止指令。
参与者超时:

  1. precommit阶段中,参与者接收不到协调者的指令会自己进行中断(cancommit阶段不存在超时,因为协调者第一次给参与者发送指令,所以在precommit阶段才是参与者第一次遇到超时的情况);
  2. docommit阶段,参与者等不到协调者的指令,参与者会自己进行提交。
    参与者超时实际上解决了单点故障问题,但引发了不一致的问题。

缺陷
在3PC模型中,如果资源管理器⽆法及时收到来⾃事务管理器发出的消息,那么资源管理
器就会执⾏提交事务的操作,⽽不是⼀直持有事务的资源并处于阻塞状态,但是这种机制会导
致数据不⼀致的问题。
如果由于⽹络故障等原因,导致资源管理器没有及时收到事务管理器发出的Abort消息,则资源
管理器会在⼀段时间后提交事务,这就导致与其他接收到Abort消息并执⾏了事务回滚操作的资
源管理器的数据不⼀致。

给我的感觉,这几种模型好像都是2PC的思想?

最终⼀致性分布式事务解决方案

最终⼀致性分布式事务解决⽅案主要⽤于不要求结果数据时刻保持⼀致、允许存在中间状态,
但经过⼀段时间后,各个节点的数据能够达到⼀致状态的场景

最终⼀致性分布式事务解决⽅案的优点如下。
1)性能⽐较⾼,这是因为最终⼀致性分布式事务解决⽅案不要求数据时刻保持⼀致,不会因
⻓时间持有事务占⽤的资源⽽消耗过多的性能。
2)具备可⽤性。
3)适合⾼并发场景。
最终⼀致性分布式事务解决⽅案的缺点如下。
1)因为数据存在短暂的不⼀致,所以在某个时刻查询出的数据状态可能会不⼀致。
2)对于事务⼀致性要求特别⾼的场景不太适⽤。

最终一致性的服务模式

可查询操作

在分布式事务的执⾏过程中,如果出现了错误,需要明确知道其他操作的处理情况。此时需要其他服务提供可查询的接⼝,以保证通过可查询的接⼝获取其他服务的处理情况。

幂等操作

为了保证数据的最终⼀致性,系统会提供很多重试操作。如果这些重试操作涉及的⽅法中,某些⽅法的实现不具有幂等性,则即使重试操作成功了,也⽆法保证数据最终⼀致性。

通常有两种实现幂等性的⽅式:⼀种是通过业务操作本身实现幂等性;另⼀种是通过系统缓存
所有的请求与处理结果,当再次检测到相同的请求时,直接返回之前缓存的处理结果

TCC

1.Try阶段
1)完成所有业务的⼀致性检查。
2)预留必要的业务资源,并需要与其他操作隔离。
2.Confirm阶段
1)此阶段会真正执⾏业务操作。
2)因为在Try阶段完成了业务的⼀致性检查,所以此阶段不会做任何业务检查。
3)只⽤Try阶段预留的业务资源进⾏操作。
4)此阶段的操作需要满⾜幂等性。
3.Cancel阶段
1)释放Try阶段预留的业务资源。
2)此阶段的操作需要满⾜幂等性。

疑问:与2PC的区别,为什么二者一个是为了实现强一致性,另一个是为了实现最终一致性。
https://zhuanlan.zhihu.com/p/419829407
https://zhuanlan.zhihu.com/p/462669898
里面提到了,TCC本质上是一个业务层面上的2PC,他要求业务在使用TCC模式时必须实现三个接口Try()、Confirm()和Cancel()
对try预留资源的理解:使得confirm和cancel幂等。如果没有try,只有confirm和cancel,则无法实现幂等。我认为冻结库存字段和预增加积分字段应该每笔订单单独一行,这样才能实现幂等,否则无法实现幂等(即使能,那try也没意义了)。为什么冻结库存为2,库存为98呢?因为要先减库存,不减的话其他事务觉得库存还有很多,造成库存的超发。积分的话由于是加的,无所谓。

但是我看到书上说,实现幂等的方式是在分⽀事务记录表中增加事务的执⾏状态,每次执⾏分⽀事务以及Confirm阶段和Cancel阶段的⽅法时,都查询此事务的执⾏状态,以此判断事务的幂等性。那我就不太理解这个冻结库存的作用了,甚至是不理解try的作用。直接减不就行了。cancel再加回来,反正都幂等了。

但是https://zhuanlan.zhihu.com/p/488213115这篇文章印证了我的思路,的确是靠冻结库存来实现幂等的。

try预留资源使得confrim和cancel幂等:cancel只需要让资源加上预留资源,然后预留资源清0;confirm只需要让预留资源清零。这样就幂等了。

为什么说TCC是最终一致性呢,因为try之后其实是存在一个中间态的。

可补偿操作

如果某些数据处于不正常的状态,需要通过某种⽅式进⾏业务补偿,使数据能够达到最终⼀致性,这种因数据不正常⽽进⾏的补偿操作,就是可补偿操作服务模式。业务服务对外提供操作数据的接⼝时,也需要对外提供补偿业务的接⼝,当其他服务调⽤业务服务操作数据的接⼝出现异常时,能够通过补偿接⼝进⾏业务补偿操作

可靠消息

可靠消息最终⼀致性分布式事务解决⽅案指的是事务的发起⽅执⾏完本地事务之后,发出⼀条
消息,事务的参与⽅,也就是消息的消费者一定能够接收到这条消息并处理成功。这个⽅案强
调的是只要事务发起⽅将消息发送给事务参与⽅,事务参与⽅就⼀定能够执⾏成功,事务最终
达到⼀致的状态。

以电商⽀付场景,向⽤户发放优惠券为例,具体流程为订单服务向RocketMQ发送Half消息
(Half消息是RocketMQ中的概念),发送成功后,RocketMQ会向订单服务响应Half消息发送
成功的状态。接下来,订单服务执⾏本地事务,修改订单数据的状态,并向RocketMQ发送提
交事务或者回滚事务的消息。如果是提交事务的消息,则RocketMQ会向优惠券服务投递事务
消息,优惠券服务收到消息后,会执⾏为⽤户发放优惠券的逻辑。如果是回滚消息,则
RocketMQ会删除相应的消息,不再向优惠券服务投递对应的事务消息

疑问:为啥不在订单事务成功之后直接投递消息,而是非要在订单事务执行前先发一个half
请添加图片描述
⾸先,事务发起⽅将消息发送给可靠消息服务,这⾥的可靠消息服务可以基于本地数据表实
现,也可以基于消息队列中间件实现。然后,事务参与⽅从可靠消息服务中接收消息。事务发
起⽅和可靠消息服务之间、可靠消息服务和事务参与⽅之间都是通过⽹络进⾏通信的。由于⽹
络本身的不稳定性,可能会造成分布式事务问题,因此在实现上,需要引⼊消息确认服务和消
息恢复服务。

消息确认服务会定期检测事务发起⽅业务的执⾏状态和消息库中的数据,如果发现事务发起⽅
业务的执⾏状态与消息库中的数据不⼀致,消息确认服务就会同步事务发起⽅的业务数据和消
息库中的数据,保证数据⼀致性,确保事务发起⽅业务完成本地事务后消息⼀定会发送成功。

消息恢复服务会定期检测事务参与⽅业务的执⾏状态和消息库中的数据,如果发现事务参与⽅
业务的执⾏状态与消息库中的数据不⼀致(这⾥的不⼀致,通常指的是事务参与⽅消费消息
后,执⾏本地事务操作失败,导致事务参与⽅本地事务的执⾏状态与消息库中的数据不⼀
致),消息恢复服务就会恢复消息库中消息的状态,使消息的状态回滚为事务发起⽅发送消息
成功,但未被事务参与⽅消费的状态。

这里的消息库可以是基于数据库表的,和业务库绑定在一起;也可以是一个单独的消息中间件。

注意事项
1.事务发送⽅本地事务与消息发送的原⼦性问题
(1)原⼦性问题产⽣的原因
可靠消息最终⼀致性要求事务发起⽅的本地事务与消息发送的操作具有原⼦性,也就是事务发
起⽅执⾏本地事务成功后,⼀定要将消息发送出去(执行本地任务失败,自然就不会发消息了)。
执⾏本地事务和发送消息,要么都成功,要么都失败。
(2)原⼦性问题的解决⽅案
在实际的解决⽅案中,可以通过消息确认服务解决本地事务与消息发送的原⼦性问题。
2.事务参与⽅接收消息的可靠性问题
(1)可靠性问题产⽣的原因
由于服务器宕机、服务崩溃或⽹络异常等原因,导致事务参与⽅不能正常接收消息,或者接收
消息后处理事务的过程中发⽣异常,⽆法将结果正确回传到消息库中。此时,就会产⽣可靠性
问题。
(2)可靠性问题的解决⽅案
可以通过消息恢复服务保证事务参与⽅的可靠性。
3.事务参与⽅接收消息的幂等性问题
(1)幂等性问题产⽣的原因
在实际场景中,由于某种原因,可靠消息服务可能会多次向事务参与⽅发送消息,如果事务参
与⽅的⽅法不具有幂等性,就会造成消息重复消费的问题,这就是典型的幂等性问题。
(2)幂等性问题的解决⽅案
解决⽅案就是事务参与⽅的⽅法实现要具有幂等性,只要参数相同,⽆论调⽤多少次接⼝或⽅
法,得出的结果都与第⼀次调⽤接⼝或⽅法得出的结果相同。

分布式事务的最终一致性解决方案的核心思想:业务方提供幂等操作和查询操作(查询事务是否执行成功)

分布式事务原理

上面介绍了XA、TCC、可靠消息等分布式事务解决方案,下面主要讲述的是这几种方案的原理

XA原理

DTP模型
在这里插入图片描述

JTA规范
在这里插入图片描述

MySQL XA事务
在这里插入图片描述
1)在XA START和XA END之间执⾏的是业务SQL语句,⽆论是否执⾏成功,都应该执⾏
XA END语句。
2)在IDLE状态下的事务可以直接执⾏XA COMMIT,这⾥我们可以这样理解,当只有⼀个资源
管理器的时候,可以直接退化成⼀阶段提交。
3)只有状态为Failed的时候,才能执⾏XA ROLLBACK进⾏XA事务回滚。
4)XA事务和⾮XA事务(即本地事务)是互斥的。例如,已经执⾏了XA START命令来开启⼀
个XA事务,则本地事务不会被启动,直到XA事务被提交或回滚为⽌。相反的,如果已经使⽤
START TRANSACTION启动了⼀个本地事务,则XA语句不能被使⽤,直到该事务被提交或回
滚为⽌。
在这里插入图片描述
XA面临的问题:
单点故障:⼀旦协调者事务管理器发⽣故障,参与者资源管理器会⼀直阻塞下去。尤其在两阶段提交的第
⼆个阶段,如果协调者发⽣故障,那么所有的参与者都将处于锁定事务资源的状态中,⽆法继
续完成事务操作(如果是协调者宕机,可以重新选举⼀个协调者,但是⽆法解决因为协调者宕
机导致的参与者处于阻塞状态的问题)。
数据不一致:在Commit阶段,当协调者向参与者发送commit请求后,发⽣了局部⽹络异常或者在发送
commit请求的过程中,协调者发⽣了故障,会导致只有⼀部分参与者接收到了commit请求。⽽
这部分参与者接到commit请求之后就会执⾏commit操作,但是其他部分未接到commit请求的
参与者⽆法执⾏事务提交。于是整个分布式系统便出现了数据不⼀致性的现象。

解决思路:

  1. 为了防止事务管理器宕机后状态丢失,可以持久化的全局事务状态来恢复事务管理器的执⾏逻辑
  2. 通过记录ack表来判断资源管理器是否收到了请求,并在重启后向那些没有收到事务管理器指令的资源管理器重新发送指令。
  3. 资源管理器⻓时间没有收到事务管理器的rollback或者commit语句时,会⼀直持有数据库中相关数据的记录锁,不仅占⽤系统资源,⽽且会使得相关数据记录⽆法被其他业务修改,因此资源管理器要有⾃动回滚或者提交的功能。
  4. 事务管理器集群化部署,防止单点故障

已落地的XA规范解决方案:Atomikos、Hmily、Narayana

TCC分布式事务原理

在这里插入图片描述
在某种程度上,TCC分布式事务的三个阶段与关系型数据库的事务操作也存在类似的地⽅
在这里插入图片描述
在⼀个分布式或微服务系统中,TCC分布式事务的Try阶段是先把多个应⽤中的业务资源锁定,预留执⾏分布式事务的资源。同样,关系型数据库的DML操作会锁定数据库的⾏记录,持有数据库的资源。TCC分布式事务的Confirm操作是在所有涉及分布式事务的应⽤的Try阶段都执⾏成功后确认并提交最终事务状态的操作,⽽关系型数据库的Commit操作是在所有的DML操作执⾏成功之后提交事务。TCC分布式事务的Cancel操作是在涉及分布式事务的应⽤没有全部执⾏成功时,将已经执⾏成功的应⽤进⾏回滚,⽽关系型数据库的回滚操作是在执⾏的DML操作存在异常时执⾏的。关系型数据库中的Commit操作和Rollback操作也是⼀对反向业务操作,TCC分布式事务中的Confirm操作和Cancel操作也是⼀对反向业务操作。

TCC核心组成:
在这里插入图片描述
主业务服务是TCC分布式事务的发起⽅,在下单扣减库存的业务场景中,订单服务是TCC分布
式事务的发起⽅,就是主业务服务。

从业务服务主要负责提供TCC业务操作,是整个业务活动的操作⽅。从业务活动必须实现TCC
分布式事务Try、Confirm和Cancel三个阶段的接⼝,供主业务服务调⽤。由于在TCC分布式事
务的执⾏过程中,Confirm阶段的操作和Cancel阶段的操作可能会被执⾏多次,因此需要
Confirm阶段的操作和Cancel阶段的操作保证幂等性。

TCC管理器在整个TCC分布式事务的执⾏过程中,管理并控制着整个事务活动,包括记录并维
护TCC全局事务的事务状态和每个从业务服务的分⽀事务状态,并在参与分布式事务的所有分
⽀事务的Try阶段都执⾏成功时,⾃动调⽤每个分⽀事务的Confirm阶段的操作,完成分布式事
务,同时会在参与分布式事务的某些分⽀事务执⾏失败时,⾃动调⽤分⽀事务的Cancel操作回
滚分布式事务。

在使⽤TCC分布式事务解决分布式场景下的数据⼀致性问题时,需要将原本的⼀个事务接⼝改
造成三个不同的事务逻辑,也就是前⽂说的Try阶段、Confirm阶段和Cancel阶段。
原本⼀个接⼝的⽅法完成的事务逻辑也要分拆成如下执⾏流程。
1)依次执⾏所有参与TCC分布式事务的分⽀事务Try阶段的操作。
2)如果每个分⽀事务Try阶段的逻辑都执⾏成功,则TCC分布式事务管理器会⾃动调⽤每个分
⽀事务Confirm阶段的⽅法并执⾏,完成整个分布式事务的逻辑。
3)如果某个分⽀事务的Try逻辑或者Confirm逻辑的执⾏出现问题,则TCC分布式事务管理器
会⾃动感知这些异常信息,然后⾃动调⽤每个分⽀事务Cancel阶段的⽅法执⾏Cancel逻辑,回
滚之前执⾏的各种操作,使数据恢复到执⾏TCC分布式事务之前的状态。

讲得直⽩点,就是如果遇到如下情况,TCC分布式事务会在Try阶段检查参与分布式事务的各个
服务、数据库和资源是否都能够保证分布式事务正常执⾏,能否将执⾏分布式事务的资源预留
出来,⽽不是先执⾏业务逻辑操作。
1)数据库或其他数据存储服务宕机,例如下单扣减库存时,库存数据库宕机了。
2)某个应⽤服务宕机,例如下单扣减库存时,库存服务宕机了。
3)参与分布式事务的资源不⾜,例如下单扣减库存时,商品库存不⾜。

如果参与分布式事务的服务都正常执⾏了,也就是说,数据库或其他数据存储能够正常提供服
务,所有参与分布式事务的应⽤服务正常,执⾏分布式事务时需要的资源充⾜,并且在Try阶段
顺利预留出执⾏分布式事务需要的资源,再执⾏TCC分布式事务的Confirm阶段,就能够⼤概
率保证分布式事务成功执⾏。

如果在Try阶段,某个服务执⾏失败了,可能是数据库或者其他数据存储宕机了,或者是这个服
务宕机了,也有可能是这个服务对应的数据资源不⾜。此时,会⾃动执⾏各个服务Cancel阶段
的逻辑,回滚Try阶段执⾏的业务逻辑,将数据恢复到执⾏分布式事务之前的状态。

其实,通过上⾯的逻辑,TCC分布式事务还是不能保证执⾏结果数据的⼀致性。这⾥存在着⼀
个问题,那就是如果发⽣了异常情况,例如,在下单扣减库存的业务场景中,订单服务突然宕
机,然后重启订单服务,TCC分布式事务如何保证之前没有执⾏完的事务继续执⾏呢?
这种问题在实际的业务场景中是经常出现的,在设计TCC分布式事务框架时必须要考虑这种异
常场景。在执⾏TCC分布式事务时,需要记录⼀些分布式事务的活动⽇志,将这些活动⽇志存
储到⽂件或者数据库中,将分布式事务的各个阶段和每个阶段执⾏的状态全部记录下来。
在这里插入图片描述

TCC业务场景举例

在电商业务场景中,⼀个典型的业务场景就是⽀付订单。这个场景包含修改订单状态、扣减存库、增加积分、创建出库单等业务,这些业务要么全部执⾏成功,要么全部执⾏失败,必须是⼀个完整的事务。如果不能构成⼀个完整的事务,就有可能出现库存未扣减或者超卖的问题,也就是如下图的1、2、3、4操作必须全部成功或全部失败
在这里插入图片描述
Try阶段
在这里插入图片描述
Confirm阶段
在这里插入图片描述
Cancel阶段
在这里插入图片描述

可靠消息最终⼀致性分布式事务原理

可靠消息最终⼀致性的基本原理是事务发起⽅(消息发送者)执⾏本地事务成功后发出⼀条消息,事务参与⽅(消息消费者)接收到事务发起⽅发送过来的消息,并成功执⾏本地事务。事务发起⽅和事务参与⽅最终的数据能够达到⼀致的状态。
这⾥主要强调如下两点。
1)事务发起⽅⼀定能够将消息成功发送出去。
2)事务参与⽅⼀定能够成功接收到消息。

在这里插入图片描述
⽹络的不确定性可能会造成事务发起⽅发送消息失败,也可能会造成事务参与⽅接收消息失败,即造成分布式事务的问题。

本地消息表

为了防⽌在使⽤消息⼀致性⽅案处理分布式事务的过程中出现消息丢失的情况,使⽤本地事务保证数据业务操作和消息的⼀致性,也就是通过本地事务,将业务数据和消息数据分别保存到本地数据库的业务数据表和本地消息表中,然后通过定时任务读取本地消息表中的消息数据,将消息发送到消息中间件,等到消息消费者成功接收到消息后,再将本地消息表中的消息删除。这种⽅式实现的分布式事务就是基于本地消息表的可靠消息最终⼀致性分布式事务。

核心思想:将分布式系统同步数据的操作通过消息或者日志的形式异步执行,并定时重试,需要保证幂等性。存放消息的本地消息表和存放数据的业务数据表位于同⼀个数据库中,这种设计能够保证使⽤本地事务达到消息和业务数据的一致性

流程:
第⼀步:事务发起⽅向业务数据表成功写⼊数据后,会向本地消息表发送⼀条消息数据,因为写业务数据和写消息数据在同⼀个本地事务中,所以本地事务会保证这条消息数据⼀定能够正确地写⼊本地消息表。
第⼆步:使⽤专⻔的定时任务将本地消息表中的消息写⼊消息中间件,如果写⼊成功,会将消息从本地消息表中删除。否则,继续根据⼀定的规则进⾏重试操作。
第三步:如果消息根据⼀定的规则写⼊消息中间件仍然失败,可以将失败的消息数据转储到“死信”队列数据表中,后续进⾏⼈⼯⼲预,以达到事务最终⼀致性的⽬的。
第四步:事务参与⽅,也就是消息消费者会订阅消息中间件的消息,当接收到消息中间件的消息时,完成本地的业务逻辑。
第五步:事务参与⽅的本地事务执⾏成功,则整个分布式事务执⾏成功。否则,会根据⼀定的规则进⾏重试。如果仍然不能成功执⾏本地事务,则会给事务发起⽅发送⼀条事务执⾏失败的消息,以此来通知事务发起⽅进⾏事务回滚。
在这里插入图片描述
可靠消息最终⼀致性分布式事务解决⽅案是处理分布式事务的典型⽅案,也是业界使⽤⽐较多的⼀种⽅案。基于本地消息表实现的可靠消息⽅案是其中⼀种具体实现⽅式,这种实现⽅式有如下明显的优点。
1)使⽤消息中间件在多个服务之前传递消息数据,在⼀定程度上避免了分布式事务的问题。
2)作为业界使⽤⽐较多的⼀种⽅案,相对⽐较成熟。
也有⽐较明显的缺点,如下所示。
1)⽆法保证各个服务节点之间数据的强⼀致性。
2)某个时刻可能会查不到提交的最新数据。
3)消息表会耦合到业务库中,需要额外⼿动处理很多发送消息的逻辑,不利于消息数据的扩展。如果消息表中存储了⼤量的消息数据,会对操作业务数据的性能造成⼀定的影响。
4)消息发送失败时需要重试,事务参与⽅需要保证消息的幂等。
5)如果消息重试后仍然失败,则需要引⼊⼈⼯⼲预机制。
6)消息服务与业务服务耦合,不利于消息服务的扩展和维护。
7)消息服务不能共⽤,每次需要实现分布式事务时,都需要单独开发消息服务逻辑,增加了开发和维护的成本。

独立消息服务

独⽴消息服务是在本地消息表的基础上进⼀步优化,将消息处理部分独⽴部署成单独的服务,以便消息服务能够单独开发和维护,这样就实现了消息服务和业务服务的解耦、消息数据和业务数据的解耦,⽅便对消息服务进⾏扩展。
在这里插入图片描述

第⼀步:事务发起⽅向可靠消息服务成功发送消息后,执⾏本地事务。
第⼆步:可靠消息服务接收到事务发起⽅发送的消息后,将消息存储到消息库中,并将消息记录的状态标记为“待发送”,并不会⻢上向消息中间件发送消息。同时,向事务发起⽅响应消息发送已就绪的状态。
第三步:当事务发起⽅的事务执⾏成功时,事务发起⽅会向可靠消息服务发送确认消息,否则,发送取消消息。
第四步:当可靠消息服务接收到事务发起⽅发送过来的确认消息时,会将消息发送到消息中间件,并将消息库中保存的当前消息记录状态标记为“已发送”。如果可靠消息接收到事务发起⽅发送的取消消息,会直接将消息库中保存的当前消息删除或者标记为“已删除”。
第五步:消息中间件接收到可靠消息服务发送过来的消息时,会将消息投递给业务参与⽅,业务参与⽅接收到消息后,执⾏本地事务,并将执⾏结果作为确认消息发送到消息中间件。
第六步:消息中间件将确认结果投递到可靠消息服务,可靠消息服务接收到确认消息后,根据结果状态将消息库中的当前消息记录标记为“已完成”。
第七步:如果事务发起⽅向可靠消息服务发送消息失败,会触发消息重试机制。如果重试后仍然失败,则会由消息确认服务定时校对事务发起⽅的事务状态和消息数据库中当前消息的状态,发现状态不⼀致时,采⽤⼀定的校对规则进⾏校对。
第⼋步:如果可靠消息服务向消息中间件发送消息失败,会触发消息重试机制。如果重试后仍然失败,则会由消息恢复服务根据⼀定的规则定时恢复消息库中的消息数据。

第七步的消息确认服务确保了事务发起方向可靠消息服务发送消息一定成功;第八步的消息恢复服务确保了可靠消息服务向中间件发送消息一定成功;中间件确保了向事务参与方投递消息一定成功。

使⽤独⽴消息服务实现分布式事务的优点如下所示。
1)消息服务能够独⽴部署、独⽴开发和维护。
2)消息服务与业务服务解耦,具有更好的扩展性和伸缩性。
3)消息表从本地数据库解耦出来,使⽤独⽴的数据库存储,具有更好的扩展性和伸缩性。
4)消息服务可以被多个服务共⽤,降低了重复开发消息服务的成本。
5)消息数据的可靠性不依赖于消息中间件,弱化了对于消息中间件的依赖性。
缺点如下。
1)发送⼀次消息需要请求两次接⼝。
2)事务发起⽅需要开发⽐较多的事务查询接⼝,在⼀定程度上增加了开发成本。

RocketMQ

在这里插入图片描述
RocketMQ提供的事务监听接口

public interface RocketMQLocalTransactionListener {
	RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg);
	RocketMQLocalTransactionState checkLocalTransaction(Message msg);
}

返回的是事务消息的状态:
TransactionStatus.CommitTransaction:提交事务消息,消费者可以消费此消息
TransactionStatus.RollbackTransaction:回滚事务,它代表该消息将被删除,不允许被消费。
TransactionStatus.Unknown :中间状态,它代表需要检查消息队列来确定状态

RocketMQ实现事务消息主要分为两个阶段:正常事务的发送及提交、事务信息的补偿流程
整体流程为:

正常事务发送与提交阶段
生产者发送一个半消息给MQServer(半消息是指消费者暂时不能消费的消息)
服务端响应消息写入结果,半消息发送成功
开始执行本地事务
根据本地事务的执行状态执行Commit或者Rollback操作
事务信息的补偿流程
如果MQServer长时间没收到本地事务的执行状态会向生产者发起一个确认回查的操作请求
生产者收到确认回查请求后,检查本地事务的执行状态
根据检查后的结果执行Commit或者Rollback操作
补偿阶段主要是用于解决生产者在发送Commit或者Rollback操作时发生超时或失败的情况(checkLocalTransaction的作用)
在这里插入图片描述
https://zhuanlan.zhihu.com/p/159573084

其实有个很严重的问题,RocketMQ似乎无法做到消费者消费失败情况下的回滚?
根据chatGPT所言,当MQServer向MQ Subscriber commit后,如果Subscriber执行失败并返回 TransactionStatus.Rollback,RocketMQ 将标记该消息的事务状态为回滚。当消息的事务状态被标记为回滚后,RocketMQ 将尝试重新发送该消息,以便它再次触发消息消费方的本地事务。如果一直失败,此时要么重试后记录,要么进入死信队列,最终由我们手动保证最终一致性

https://zhuanlan.zhihu.com/p/115553176

也就是说RocketMQ并不能保证分布式事务的一致性(消费者失败后无法回滚生产者)

总结来看,分布式事务的核心要点:
通知:事务参与方失败,要及时通知给事务发起方,TCC是通过框架,本地消息表是通过数据库
回滚:一旦失败,事务发起方和参与方要提供回滚接口供事务的回滚操作(RocketMQ不满足)
幂等:由于分布式环境下的网络波动,事务的提交操作和回滚操作可能需要反复进行,此时需要保证幂等性。
可查询:为了防止状态未知,阻塞等待,需要提供接口展示本地事务的执行状态
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值