seata1.5.2
1. Seata是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)。
2. Seata的三大角色
在 Seata 的架构中,一共有三个角色:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
3. Seata的模式
- AT
- TCC
- Sage
- XA
这边就不过多说明,我们这边主要使用的是AT模式,这个也是seata开源版本的选择,基于2阶段提交的思想
官网:https://seata.io/zh-cn/index.html
源码: https://github.com/seata/seata
官方Demo: https://github.com/seata/seata-samples
4. 设计亮点
相比与其它分布式事务框架,Seata架构的亮点主要有几个:
- 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
- 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
- 通过全局锁实现了写隔离与读隔离。
5. 存在的问题
性能损耗
一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一 次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而 且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。
性价比
为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败 需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应 用开发一个补偿交易是否是值得?
全局锁
热点数据 相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销 比XA的prepare低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点 数据,这个问题会更加严重。
回滚锁释放时间 Seata在回滚时,需要先删除各节点的undo log,然后才能释放TC内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。
死锁问题 Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延 长了对数据库锁的占有时间。
6. Seata快速开始 (默认nacos-server2.1.0已经已经安装配置好,我的nacos是安装在win10当中的,单机模式 127.0.0.1:8848)
1 Seata Server(TC)环境搭建
下载安装包 https://github.com/seata/seata/releases 下载1.5.2版本(各个版本安装有所不同,且seata版本要与alibaba版本对应 请知悉)
解压seata1.5.2的安装包
主要关注截图的 bin conf lib script 目录
bin 启动命令 (.sh为liunx .bat为win)
conf 目录为配置文件目录 (日志配置这边就不多说了,)
application.example.yml 这个为模版了,按照这个配置就行
application.yml 默认的是使用内置的file方式启动的,可以启动,但只适合单机配置,就不记录这种方式了(当初接触seata0.9版本的时候,玩的就是file模式,需要修改的配置就很多,现在1.5.2版本都集成到配置文件上了,方便简化了很多)
我就直接上修改好的配置文件了
server: port: 7091 # 默认端口就不修改了 spring: application: name: seata-server # 默认服务名称 就不修改了 logging: config: classpath:logback-spring.xml # 日志配置 file: path: ${user.home}/logs/seata extend: logstash-appender: destination: 127.0.0.1:4560 kafka-appender: bootstrap-servers: 127.0.0.1:9092 topic: logback_to_logstash console: # 控制台的用户名密码 user: username: seata password: seata seata: config: # support: nacos 、 consul 、 apollo 、 zk 、 etcd3 seata的配置中心方式 nacos的配置中心就不多说 都是常规配置 指定了nacos的用户密码那配置文件需要写出来 type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP username: password: ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: "" #secret-key: "" data-id: seataServer.properties registry: # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa type: nacos preferred-networks: 30.240.* nacos: application: seata-server server-addr: 127.0.0.1:8848 group: SEATA_GROUP namespace: cluster: default username: password: ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: "" #secret-key: "" server: service-port: 8091 #If not configured, the default is '${server.port} + 1000' seata服务的配置 我这边都用的模版的默认配置 max-commit-retry-timeout: -1 max-rollback-retry-timeout: -1 rollback-retry-timeout-unlock-enable: false enable-check-auth: true enable-parallel-request-handle: true retry-dead-threshold: 130000 xaer-nota-retry-timeout: 60000 vgroup-mapping: fsp_tx_group: default recovery: handle-all-session-period: 1000 undo: log-save-days: 7 log-delete-period: 86400000 session: branch-async-queue-size: 5000 #branch async remove queue size enable-branch-async-remove: false #enable to asynchronous remove branchSession store: # support: file 、 db 、 redis seata数据的保存方式,这边后续考虑高可用 选择使用数据库保存 默认是file文件 mode: db db: datasource: druid db-type: mysql driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://110.41.42.252:3308/seata_server?rewriteBatchedStatements=true user: root password: 123456 min-conn: 5 max-conn: 100 global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock query-limit: 100 max-wait: 5000 security: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
lib/jdbc目录下有2个mysql的驱动包一个是5.X 一个是8.X 这边最删除掉不用的驱动包(mysql服务的版本查看这边就不多说了)
新建一个数据库我这边叫 seata_server 与配置文件对应起来 在mysql服务中执行mysql.sql 会生成4张表
表具体的作用就不过多说明
客户端连接的数据库需要增加日志表,方便数据的回滚,这边也记录下来 熟悉mysql的对undo.log 应该不陌生
-- 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(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 = utf8mb4 COMMENT ='AT transaction mode undo table';
接下来就可以启动.bat 文件了 liunx就是.sh
前提是nacos已经启动完成,且配置nacos正确 seata1.5.2对应nacos版本为2.1.0 版本要对应起来
nacos
seata
到此 seata服务就启动完成了 后续就到整合使用了
控制台地址: Http://127.0.0.1:7091
客户端搭建
这边就不多说直接上代码
创建父maven工程
pom
<properties> <java.version>8</java.version> <!-- Spring Cloud --> <spring.cloud.version>Hoxton.SR12</spring.cloud.version> <!-- Spring Boot --> <spring-boot.version>2.3.12.RELEASE</spring-boot.version> <!-- Spring Cloud alibaba --> <project.version>2.2.9.RELEASE</project.version> </properties> <!--Spring Cloud Alibaba Version--> <!--2.2.9.RELEASE--> <!--Sentinel Version--> <!--1.8.5--> <!--Nacos Version--> <!--2.1.0--> <!--RocketMQ Version--> <!--4.9.4--> <!--seata Version--> <!--1.5.2--> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <!-- Spring Dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.9.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
事务分支1
pom
<dependencies>
<!--seata 分布式事务组件-->
<!--<dependency>-->
<!--<groupId>com.alibaba.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-alibaba-seata</artifactId>-->
<!--</dependency>-->
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件+actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatisPlus 核心库 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!--seata 分布式事务组件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
application.yml
server: port: 8090 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://110.41.42.252:3308/test003?useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai username: root password: 123456 application: name: mybatis-service cloud: nacos: discovery: namespace: public server-addr: 127.0.0.1:8848 #开启日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开放端口 management: endpoints: web: exposure: include: "*" # seata日志级别 logging: level: io: seata: info seata: registry: # 配置seata的注册中心, 告诉seata client 怎么去访问seata server(TC) type: nacos nacos: server-addr: 127.0.0.1:8848 # seata server 所在的nacos服务地址 application: seata-server # seata server 的服务名seata-server ,如果没有修改可以不配 username: nacos password: nacos group: SEATA_GROUP # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配 config: type: nacos nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos group: SEATA_GROUP tx-service-group: fsp_tx_group #这里每个服务都是对应不同的映射名,在配置中心可以看到 service: vgroup-mapping: fsp_tx_group: default
最基础的代码就不写了
事务分支2 使用openfeign接口调用事务分支1
pom
<dependencies>
<!--seata 分布式事务组件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<!--openfeign客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件+actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatisPlus 核心库 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
application.yml
server: port: 8092 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://110.41.42.252:3308/test002?useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai username: root password: 123456 application: name: seata-service cloud: nacos: discovery: namespace: public server-addr: 127.0.0.1:8848 #开启日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开放端口 management: endpoints: web: exposure: include: "*" # seata日志级别 logging: level: com.nicong.feign: debug io: seata: info # feign饥饿加载配置 ribbon: eager-load: enabled: true clients: mybatis-service feign: client: config: mybatis-service: #服务名称 connectTimeout: 5000 #默认2秒 readTimeout: 10000 #默认5秒: loggerLevel: full seata: registry: # 配置seata的注册中心, 告诉seata client 怎么去访问seata server(TC) type: nacos nacos: server-addr: 127.0.0.1:8848 # seata server 所在的nacos服务地址 application: seata-server # seata server 的服务名seata-server ,如果没有修改可以不配 username: nacos password: nacos group: SEATA_GROUP # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配 config: type: nacos nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos group: SEATA_GROUP tx-service-group: fsp_tx_group #这里每个服务都是对应不同的映射名,在配置中心可以看到 service: vgroup-mapping: fsp_tx_group: default
启动类需要额外加上
@EnableFeignClients 开启远程调用支持
具体的代码实现就不写了 基础性的东西
现在最重要的一步
给事务分组
按照配置文件中的
tx-service-group: fsp_tx_group #这里每个服务都是对应不同的映射名,在配置中心可以看到
fsp_tx_group: default
登录到naocs中配置
service.vgroupMapping.fsp_tx_group:default
保存发布就可以了
启动2个事务分支(确保naocs seata正常)
观查 seata窗口:
启动分支1 可以看到seata已经生成 TM RM维护分支
启动分支2 同样可以看到TM RM生成
上面提到过每个事务分支的数据库需要加上日志表的不要忘记,后续的演示就不截图了
在service 实现方法接口上加上 @GlobalTransactional 就可以了
使用feign接口调用另外一个接口如果报错,数据库的数据都会对应的进行回滚,基于日志表
打完收工