一、 Seata简介

1 Seata的由来

随着互联网项目的发展,分布式架构的项目显示出了独特的独特的魅力。但是分布式事务问题在分布式架构项目中越显重要。
2019年1月阿里巴巴团队发起了开源项目Fescar(Fast & Easy Commit And Rollback )与社区共建分布式事务解决方案。Fescar的愿景是:让分布式事务可以像本地事务一样,简单和高效。
随着Fescar社区发展,蚂蚁金服加入Fescar社区,并在0.4.0版本中贡献了TCC模式。
为了打造更加中立、更开放、生态更丰富的分布式事务解决方案。经社区成员投票,对Fescar升级,命名为Seata,意味着 Simple Extensible Autonomous Transaction Architecture,简单的可扩展自治事务体系结构。
目前Seata源码托管在github中。

https://github.com/seata/seata/

Seata中文官网地址

http://seata.io/zh-cn/

Spring Cloud Alibaba Seata分布式事务 - 图1

2 官方对Seata的解释

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。

3 Seata的发展历程图

Spring Cloud Alibaba Seata分布式事务 - 图2

二、 Seata支持的事务模式

1 Seata AT模式

1.1 适用

适用于支持本地ACID事务的关系型数据库。
对于Java应用适用于通过JDBC可以访问的数据库。
具体数据库:MySQL、Oracle、PostgreSQL、TiDB

1.2 总体实现

总体是基于二阶段提交协议演变而来。
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:提交异步化,尽可能保证性能高。回滚通过一阶段日志进行反向补偿。

1.3 写隔离

· 一阶段本地事务提交前,需要确保先拿到 全局锁 。
· 拿不到 全局锁 ,不能提交本地事务。
· 拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

1.3.1 写隔离举例说明

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
Spring Cloud Alibaba Seata分布式事务 - 图3
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
Spring Cloud Alibaba Seata分布式事务 - 图4
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

1.4 读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
Spring Cloud Alibaba Seata分布式事务 - 图5
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

1.5 具体示例

给定表product
Spring Cloud Alibaba Seata分布式事务 - 图6
AT模式中需要执行的SQL
Spring Cloud Alibaba Seata分布式事务 - 图7

1.5.1 一阶段
  1. 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = ‘TXC’)等相关的信息。
    2. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
    Spring Cloud Alibaba Seata分布式事务 - 图8
    3. 执行业务 SQL:更新这条记录的 name 为 ‘GTS’。
    4. 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
    Spring Cloud Alibaba Seata分布式事务 - 图9
    5. 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。
    6. 提交前,向 TC(Seata-server) 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁 。
    7. 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
    8. 将本地事务提交的结果上报给 TC(Seata-server)。
    1.5.2 二阶段回滚
  2. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
    2. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
    3. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
    4. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
    update product set name = ‘TXC’ where id = 1;
    5. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
    1.5.3 二阶段提交
  3. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
    2. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

    1.6 回滚日志表

    UNDO_LOG 为回滚日志表。
    Spring Cloud Alibaba Seata分布式事务 - 图10

    2 Seata TCC模式

    2.1 适用

    TCC适用于任何需要做分布式事务的场景。
    由于事务回滚和事务提交的逻辑需要由程序员编写,所以适用AT模式的事务交给AT进行处理,其他不支持ACID或不支持JDBC连接的数据库(数据存储工具)可以使用TCC模式。

    2.2 总体实现

    总体依然基于二阶段提交。
    一阶段:认为是prepare行为
    二阶段:commit或rollback行为。
    TCC 模式,不依赖于底层数据资源的事务支持:
    一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
    二阶段 commit 行为:调用 自定义 的 commit 逻辑。
    二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

    2.3 实现说明

    在需要使用TCC事务的方法基础上额外需要提供commit方法或rollback方法。
    定义好方法,交给Seata Server进行根据一阶段结果进行回调commit或rollback。
    Spring Cloud Alibaba Seata分布式事务 - 图11

    3 Seata Saga模式

    3.1 适用

    长事务:运行时间比较长,长时间未提交的事务,也可以称之为大事务。这类事务往往会造成大量的阻塞和锁超时,容易造成主从延迟,要尽量避免使用长事务。
    理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)
    · 业务流程长、业务流程多
    · 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

    3.2 优势

    一阶段提交本地事务,无锁,高性能
    事件驱动架构,参与者可异步执行,高吞吐
    补偿服务易于实现

    3.3 缺点

    不保证隔离性

    3.4 具体实现

    3.4.1 基于状态机引擎的 Saga 实现:
    目前SEATA提供的Saga模式是基于状态机引擎来实现的,机制是:
    1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
    2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
    3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚
    注意: 异常发生时是否进行补偿也可由用户自定义决定
    4. 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能
    Spring Cloud Alibaba Seata分布式事务 - 图12

    4 Seata XA 模式

    4.1 适用

    · 支持XA 事务的数据库。
    · Java 应用,通过 JDBC 访问数据库。

    4.2 整体实现

    在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
    Spring Cloud Alibaba Seata分布式事务 - 图13

    执行阶段:
    可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
    持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)
    完成阶段:
    分支提交:执行 XA 分支的 commit
    分支回滚:执行 XA 分支的 rollback

    4.3 代码实现

    XA模式和AT模式代码实现可以说几乎是一样的。唯一的区别是在代码中需要配置数据源代理。
@Bean(“dataSource”)
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}




三、 基于Docker 安装Seata Server

拉取镜像
# docker pull seataio/seata-server:1.4.2
创建网卡(如果已经创建则不用创建)
# docker network create alibaba
创建mysql容器。如果mysql已经创建过,此步骤省略
# docker run -d —name mysqld -p 3306:3306 —net alibaba -e MYSQL_ROOT_PASSWORD=root mysql
修改mysql密码
# docker exec -it mysqld bash
# mysql -uroot -p
# use mysql
# alter user ‘root’@’%’ identified with mysql_native_password by ‘root’;
# flush privileges;
执行脚本,建议根据具体版本,从官方源码中找sql脚本,源码网址: https://github.com/seata/seata
使用navicat连接刚创建的mysql。创建数据库seata并运行当天软件中seata.sql
Spring Cloud Alibaba Seata分布式事务 - 图14
创建seata server容器
# docker run -d —name seata -p 8091:8091 -e SEATA_IP=192.168.137.128 -e SEATA_PORT=8091 —net alibaba seataio/seata-server:1.4.2

把seata容器中配置文件复制出来一份。
# docker cp seata:/seata-server/resources /usr/local



1 新版本配置方式(1.5.x)

修改resources中的配置文件application.yml,可以参考application.example.yml文件。
# cd /usr/local/resources/
# vim application.yml
修改下述内容:

server: # tomcat服务器端口号
port: 8091

spring:
application:
name: seata-server # 应用名称

logging:
config: classpath:logback-spring.xml # logback日志配置文件名
file:
path: ${user.home}/logs
# extend:
#logstash-appender:
# destination: 127.0.0.1:4560
#kafka-appender:
# bootstrap-servers: 127.0.0.1:9092
# topic: logback_to_logstash

seata:
config: # 分布式配置中心
# support: nacos 、 consul 、 apollo 、 zk 、 etcd3
type: file
registry: # 注册中心
# support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
type: nacos # 使用nacos注册中心
nacos:
application: seata-server # 注册的服务名称,和spring.application.name一样
server-addr: 192.168.137.128:8848
group: SEATA_GROUP
cluster: default
store: # 存储方式。
# support: file 、 db 、 redis
mode: db # 数据库存储方式
session: # 会话数据存储方式
mode: db
lock: # 锁数据存储方式
mode: db
db: # 配置数据库
datasource: druid # 数据库连接池类型
db-type: mysql # 数据库类型
driver-class-name: com.mysql.cj.jdbc.Driver # 驱动类
url: jdbc:mysql://192.168.137.128:3306/seata?rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
user: root
password: root
min-conn: 5 # 连接池最小连接数
max-conn: 10 # 连接池最大连接数
global-table: global_table # 全局表
branch-table: branch_table # 分支表
lock-table: lock_table # 锁定表
distributed-lock-table: distributed_lock # 分布式锁表
query-limit: 100 # 最大单次查询数据行数。默认提供分页逻辑limit 100
max-wait: 5000 # 最大等待时长,超时时间,单位毫秒

2 老版本配置方式(1.4)

修改resources中配置文件。
# cd /usr/local/resources
修改文件四处,指定使用nacos作为注册中心
# vim registry.conf
Spring Cloud Alibaba Seata分布式事务 - 图15
修改配置文件,设置JDBC四个参数,对应刚刚安装好的MySQL
# vim file.conf
Spring Cloud Alibaba Seata分布式事务 - 图16

mode=”db”

dbType = “mysql”
driverClassName = “com.mysql.cj.jdbc.Driver”
url = “jdbc:mysql://192.168.8.128:3306/seata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8”
user = “root”
password = “smallming”


把resources文件夹重新复制到容器内部。
# cd ..
# docker cp resources seata:/seata-server

3 其他设置


注意驱动版本问题:如果连接的数据库MySQL版本是8.则需要修改seata容器中的驱动包。
驱动所在位置: seata容器的/seata-server/libs目录中。
删除原有驱动包 mysql-connector-java-5.1.38.jar
上传新的驱动包 mysql-connector-java-8.x.x.jar
驱动版本错误,抛出异常,NullPointerException,信息为: getServerCharset()代码出错。

重启seata容器
# docker restart seata
查看nacos,如果发现seata server 注册成功,说明安装成功
Spring Cloud Alibaba Seata分布式事务 - 图17
数据库中有Seata-server的连接。
执行查询语句: show processlist;
Spring Cloud Alibaba Seata分布式事务 - 图18

四、 搭建基础环境

需求:
编写老师服务teacher,实现对老师数据的新增。
编写学生服务student,实现对学生信息的新增。通过Dubbo调用老师服务接口,同时新增老师数据。
使用Dubbo进行远程调用。
最终项目结构图截图:
Spring Cloud Alibaba Seata分布式事务 - 图19

1 数据库准备

按照下图创建两个表
Spring Cloud Alibaba Seata分布式事务 - 图20

2 实现teacher的新增。

2.1 新建父项目

新建Maven项目,命名为SeataParent

2.1.1 配置pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.5.RELEASE</version> </parent>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR9</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.3.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.5.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> </dependencies>

2.2 新建pojo项目

在SeataParent项目下新建pojo项目

2.2.1 新建老师实体类

新建com.bjsxt.pojo.Teacher

@Data
public class Teacher implements Serializable {
private Long id;
private String name;
}

2.2.2 新建学生实体类

新建com.bjsxt.pojo.Student

@Data
public class Student implements Serializable {
private Long id;
private String name;
private Long tid;
}

2.3 新建api项目

2.3.1 配置pom.xml

添加对pojo项目的依赖

<dependencies> <dependency> <artifactId>pojo</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>

2.3.2 新建接口

新建com.bjsxt.api.TeacherAPI

public interface TeacherAPI {
int insert(Teacher teacher);
}


2.4 新建teacher项目

2.4.1 配置pom.xml
<dependencies> <dependency> <artifactId>api</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies>

2.4.2 新建配置文件

新建application.yml

dubbo:
protocol:
name: dubbo
port: -1
registry:
address: nacos://192.168.8.128:8848
spring:
application:
name: dubbo-teacher
cloud:
nacos:
discovery:
server-addr: 192.168.8.128:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/bjsxt?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: smallming

2.4.3 新建mapper

新建com.bjsxt.mapper.TeacherMapper

@Mapper
public interface TeacherMapper {
@Insert(“insert into teacher values(default,#{name})”)
int insert(Teacher teacher);
}

2.4.4 新建service

新建com.bjsxt.service.impl.TeacherServiceImpl实现类

@DubboService public class TeacherServiceImpl implements TeacherAPI {
@Autowired
private TeacherMapper teacherMapper;
@Override
public int insert(Teacher teacher) {
return teacherMapper.insert(teacher);
}
}

2.4.5 新建控制器

新建com.bjsxt.controller.TeacherController

@RestController
public class TeacherController {
@Autowired
private TeacherAPI teacherAPI;
@RequestMapping(“/insert”)
public int insert(Teacher teacher){
return teacherAPI.insert(teacher);
}
}

2.4.6 新建启动类

新建com.bjsxt.TeacherApplication

@SpringBootApplication
@EnableDubbo public class TeacherApplication {
public static void main(String[] args) {
SpringApplication.run(TeacherApplication.class,args);
}
}

2.5 测试效果

Spring Cloud Alibaba Seata分布式事务 - 图21

3 实现学生新增并调用老师服务

3.1 编写pom.xml

<dependencies> <dependency> <artifactId>api</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies>

3.2 新建配置文件

新建application.yml

dubbo:
protocol:
name: dubbo
port: -1
registry:
address: nacos://192.168.8.128:8848

spring:
application:
name: dubbo-student
cloud:
nacos:
discovery:
server-addr: 192.168.8.128:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://localhost:3306/bjsxt?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: smallming

server:
port: 8081

3.3 新建mapper

新建com.bjsxt.mapper.StudentMapper

@Mapper
public interface StudentMapper {
@Insert(“insert into student values(default,#{name},#{tid})”)
int insert(Student student);
}

3.4 新建service

新建com.bjsxt.service.StudentService及实现类

public interface StudentService {
int insert(Student student, Teacher teacher);
}
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@DubboReference
private TeacherAPI teacherAPI;
@Override
public int insert(Student student, Teacher teacher) {
int index = studentMapper.insert(student);
if(index==1){
return teacherAPI.insert(teacher);
}
return 0;
}
}

3.5 新建控制器

新建com.bjsxt.controller.StudentController

@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping(“/insert”)
public int insert(Student student, Teacher teacher){
return studentService.insert(student,teacher);
}
}

3.6 测试结果

访问: http://localhost:8081/insert?name=abc&tid=1
查看teacher表和student都新增一条数据。老师姓名和学生姓名都是abc。

五、 结合Seata实现AT模式分布式事务

1 执行SQL脚本

找官方github 源码中的script/client/db/mysql.sql即可。

老版本建表语句
CREATE TABLE IF NOT EXISTS undo_log
(
branch_id BIGINT(20) NOT NULL COMMENT ‘branch transaction id’,
xid VARCHAR(100) NOT NULL COMMENT ‘global transaction id’,
context VARCHAR(128) NOT NULL COMMENT ‘undo_log context,such as serialization’,
rollback_info LONGBLOB NOT NULL COMMENT ‘rollback info’,
log_status INT(11) NOT NULL COMMENT ‘0:normal status,1:defense status’,
log_created DATETIME(6) NOT NULL COMMENT ‘create datetime’,
log_modified DATETIME(6) NOT NULL COMMENT ‘modify datetime’,
UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT =’AT transaction mode undo table’;
新版本建表语句 1.3.2
CREATE TABLE undo_log (
id bigint NOT NULL AUTO_INCREMENT,
branch_id bigint NOT NULL,
xid varchar(100) NOT NULL,
context varchar(128) NOT NULL,
rollback_info longblob NOT NULL,
log_status int NOT NULL,
log_created datetime NOT NULL,
log_modified datetime NOT NULL,
ext varchar(100) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY ux_undo_log (xid,branch_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
新版本建表语句 1.4
— for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS undo_log
(
branch_id BIGINT NOT NULL COMMENT ‘branch transaction id’,
xid VARCHAR(128) NOT NULL COMMENT ‘global transaction id’,
context VARCHAR(128) NOT NULL COMMENT ‘undo_log context,such as serialization’,
rollback_info LONGBLOB NOT NULL COMMENT ‘rollback info’,
log_status INT(11) NOT NULL COMMENT ‘0:normal status,1:defense status’,
log_created DATETIME(6) NOT NULL COMMENT ‘create datetime’,
log_modified DATETIME(6) NOT NULL COMMENT ‘modify datetime’,
UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT =’AT transaction mode undo table’;


2 在Teacher项目做如下修改

2.1 修改pom.xml

添加seata依赖

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>

2.2 修改配置文件

红色部分为修改的地方。
注意:seata.service.vgroup-mapping.xxxx 中xxxx的值必须是spring.cloud.alibaba.seata.tx-service-group的值

dubbo:
protocol:
name: dubbo
port: -1
registry:
address: nacos://192.168.8.128:8848
timeout: 10000
consumer:
timeout: 30000

spring:
application:
name: dubbo-teacher
cloud:
nacos:
discovery:
server-addr: 192.168.8.128:8848
alibaba:
seata:
tx-service-group: demo-tx-group2
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://localhost:3306/bjsxt?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: smallming
server:
port: 8082
seata:
service:
vgroup-mapping:
demo-tx-group2: default
grouplist:
default: 192.168.8.128:8091
disable-global-transaction: false
enabled
: true

2.3 修改service实现类

修改com.bjsxt.service.impl.TeacherServiceImpl
在方法上添加@Transactional注解

@Override
@Transactional
public int insert(Teacher teacher) {
return teacherMapper.insert(teacher);
}

3 修改student

student项目修改的步骤和teacher项目修改的流程一样。只是需要在事务入口方法上添加@GloabalTransaction注解

3.1 修改pom.xml

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>

3.2 修改配置文件

dubbo:
protocol:
name: dubbo
port: -1
registry:
address: nacos://192.168.8.128:8848
timeout: 10000
consumer:
timeout: 30000
provider:
timeout: 30000
spring:
application:
name: dubbo-student
cloud:
nacos:
discovery:
server-addr: 192.168.8.128:8848
alibaba:
seata:
tx-service-group: demo-tx-group datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://localhost:3306/bjsxt?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: smallming


server:
port: 8081

seata:
service:
vgroup-mapping:
demo-tx-group: default
grouplist:
default: 192.168.8.128:8091
disable-global-transaction: false
enabled
: true

3.3 修改实现类

修改com.bjsxt.service.impl.StudentServiceImpl
在方法上添加注解,并在方法中添加异常,查看teacher是否回滚。

@Override
@GlobalTransactional @Transactional
public int insert(Student student, Teacher teacher) {
int index = studentMapper.insert(student);
if(index==1){
int index2 = teacherAPI.insert(teacher);
int i = 5/0; return index2;
}
return 0;
}

4 测试效果

在浏览器输入http://localhost:8081/insert?name=d&tid=1
如果teacher表没有新增数据,说明seata生效了。
Spring Cloud Alibaba Seata分布式事务 - 图22

六、 添加班级classes服务

1 修改pojo项目

新建com.bjsxt.pojo.Classes

@Data
public class Classes implements Serializable {
private String id;
private String name;
}

2 修改api项目

添加依赖。此依赖是为了让接口中注解能编译通过。

<dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.3.0</version> </dependency>


新建com.bjsxt.api.ClassesAPI。
@LocalTCC必须放入接口上。表示该接口方法由seata进行托管。
@TwoPhaseBusinessAction 表示配置此方法为try方法,并配置了成功和失败的回调方法。

@LocalTCC
public interface ClassesAPI {
@TwoPhaseBusinessAction(name=“insert”,commitMethod = “mycommit”,rollbackMethod = “myrollback”)
int insert(Classes classes);
void mycommit(BusinessActionContext context);
void myrollback(BusinessActionContext context);
}

3 新建classes项目

3.1 配置pom.xml

添加Redis相关依赖。保证项目能够访问Redis。
MyBatis依赖和MySQL驱动类保留的原因seata需要有DataSource实例。

<dependencies> <dependency> <artifactId>api</artifactId> <groupId>com.bjsxt</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>

3.2 新建配置文件

新建application.yml.
配置内容与student和teacher项目相比,主要是多了redis的配置内容。

dubbo:
protocol:
name: dubbo
port: -1
registry:
address: spring-cloud://192.168.8.128:8848
timeout: 10000
consumer:
timeout: 30000
provider:
timeout: 30000
spring:
application:
name: dubbo-classes
cloud:
nacos:
discovery:
server-addr: 192.168.8.128:8848
alibaba:
seata:
tx-service-group: classes-tx-group
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://localhost:3306/bjsxt?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: smallming
data:
redis:
host: 192.168.137.128 server:
port: 8083

seata:
service:
vgroup-mapping:
classes-tx-group: default
grouplist:
default: 192.168.8.128:8091
disable-global-transaction: false
enabled
: true

3.3 新建service实现类

新建com.bjsxt.service.impl.ClassesServiceImpl

@DubboService
public class ClassesServiceImpl implements ClassesAPI {
@Autowired
private RedisTemplate redisTemplate;

@Override
@Transactional
public int insert(Classes classes) {
redisTemplate.set(classes.getId(), classes);
return 1;
}

public void mycommit(BusinessActionContext context){
System.out.println(“mycommit:”);
}

public void myrollback(BusinessActionContext context){
System.out.println(“myrollback:”);
}
}

3.4 新建控制器

新建com.bjsxt.controller.ClassesController

@RestController
public class ClassesController {
@Autowired
private ClassesAPI classesAPI;

@RequestMapping(“/insert”)
public int demo(Classes classes){
return classesAPI.insert(classes);
}
}

3.5 新建启动器

新建com.bjsxt.ClassesApplication

@SpringBootApplication
@EnableDubbo
public class ClassesApplication {
public static void main(String[] args) {
SpringApplication.run(ClassesApplication.class,args);
}
}

4 修改student项目

修改com.bjsxt.service.impl.StudentServiceImpl。
调用classes的服务。实现

@Override
@GlobalTransactional
@Transactional
public int insert(Student student, Teacher teacher, Classes classes) {
int index = studentMapper.insert(student);
if(index==1){
int index2 = teacherAPI.insert(teacher);
if(index2==1){
int index3 = classesAPI.insert(classes);
int i = 5/0;
return index3;
}
}
return 0;
}

5 测试结果

在浏览器访问student控制器。
如果发现teacher表、student表、及Redis的数据都没有新增。说明Seata生效。