ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## :-: springcloud 整合Seata实现分布式事务 ## Seata简介 [https://github.com/seata](https://github.com/seata) Seata(Simple Extensible Autonomous Transaction Architecture) 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务 0 侵入的方式,解决微服务场景下面临的分布式事务问题。 ## Seata解决方案 先介绍 `Seata` 分布式事务的几种角色: * `Transaction Coordinator(TC)`:  全局事务协调者,用来协调全局事务和各个分支事务(不同服务)的状态, 驱动全局事务和各个分支事务的回滚或提交。 * `Transaction Manager™`:  事务管理者,业务层中用来开启/提交/回滚一个整体事务(在调用服务的方法中用注解开启事务)。 * `Resource Manager(RM)`:  资源管理者,一般指业务数据库代表了一个分支事务(`Branch Transaction`),管理分支事务与 `TC` 进行协调注册分支事务并且汇报分支事务的状态,驱动分支事务的提交或回滚。 典型的分布式事务周期包括以下步骤: * TM向TC请求开启一个全局事务,TC给TM返回一个全局事务的XID; * XID在微服务调用链之间传递; * RM向TC注册XID下的分支事务; * TM根据XID向TC发出提交或者回滚的请求; * TC根据XID使RM提交或者回滚。 Seata 也是从两段提交演变而来的一种分布式事务解决方案,提供了 AT、TCC、SAGA 和 XA 等事务模式,这里重点介绍 AT模式。 #### AT模式 1、基于支持本地 ACID 事务的关系型数据库,整体机制是2PC的演变。 ~~~ 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。 二阶段:提交异步化,成功则批量地删除相应回滚日志记录,回滚则通过回滚日志进行反向补偿。 ~~~ 2、写隔离。 ~~~ 1)一阶段本地事务提交前,需要确保先拿到全局锁。 2)拿不到全局锁,不能提交本地事务;拿到全局锁,提交本地事务并插入undo_log记录。 3)拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并根据undo_log记录回滚本地事务,释放本地锁。 ~~~ 3、读隔离。 ~~~ 1)在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式) 的默认全局隔离级别是 读未提交(Read Uncommitted)。 理解:在全局事务提交之前,本地事务会先提交,这时候查询数据,对于本地库是Read Committed, 对于全局来说是 Read Uncommitted。 2)如果要求全局的读已提交,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。 ~~~ ## **springcloud 整合Seata实现分布式事务** 项目使用的是SpringBoot 2.x + SpringCloud,注册中心使用的是nacos ### **一、seata-server搭建** 1、下载最新版seata-server-1.4.0.zip 2、修改registry.conf。 registry.type 改成nacos 然后配置nacos 如下 ``` registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "" password = "" } ``` 3、使用file.conf,store.mode选择db,修改连接的数据库属性,其他的默认即可。 ``` db { datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://*************.mysql.rds.aliyuncs.com:3306/hj_seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull&useSSL=false" user = "root" password = "****" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } ``` 4、在数据库中加入三个表。 ``` DROP TABLE IF EXISTS `branch_table`; CREATE TABLE `branch_table` ( `branch_id` bigint(20) NOT NULL, `xid` varchar(128) NOT NULL, `transaction_id` bigint(20) DEFAULT NULL, `resource_group_id` varchar(32) DEFAULT NULL, `resource_id` varchar(256) DEFAULT NULL, `lock_key` varchar(128) DEFAULT NULL, `branch_type` varchar(8) DEFAULT NULL, `status` tinyint(4) DEFAULT NULL, `client_id` varchar(64) DEFAULT NULL, `application_data` varchar(2000) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `global_table` ( `xid` varchar(128) NOT NULL, `transaction_id` bigint(20) DEFAULT NULL, `status` tinyint(4) NOT NULL, `application_id` varchar(64) DEFAULT NULL, `transaction_service_group` varchar(64) DEFAULT NULL, `transaction_name` varchar(64) DEFAULT NULL, `timeout` int(11) DEFAULT NULL, `begin_time` bigint(20) DEFAULT NULL, `application_data` varchar(2000) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`xid`), KEY `idx_gmt_modified_status` (`gmt_modified`,`status`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `lock_table` ( `row_key` varchar(128) NOT NULL, `xid` varchar(96) DEFAULT NULL, `transaction_id` mediumtext, `branch_id` mediumtext, `resource_id` varchar(256) DEFAULT NULL, `table_name` varchar(32) DEFAULT NULL, `pk` varchar(32) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`row_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` 5、在bin目录下,启动server服务,默认是8091端口。 ### **二、springCloud 项目整合** **Springboot:2.3.2.RELEASE** **springCloud alibaba:2.2.0.RELEASE** **springCloud :Hoxton.RELEASE** **搭建2个微服务项目hj-seata-client1 hj-seata-client2 调用关系为 hj-seata-client调用hj-seata-client1的服务** **1、配置** ~~~ <dependencies> <dependency> <groupId>com.hjf.mall</groupId> <artifactId>hj-common</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency> <!--mysql: 数据库链接驱动工具 8.0版本可以对应数据库5.6、5.7、8.0 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--java工具类 Bean注解简化开发--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <!-- mybatis-plus start --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus-boot-starter.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>${mybatis-plus-boot-starter.version}</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> <exclusions> <exclusion> <artifactId>seata-spring-boot-starter</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <!--阿里分布式事务--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.0</version>--> </dependency> </dependencies> <dependencyManagement> <dependencies> <!--cloud依赖 --> <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>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ~~~ **application.yml** ***** ~~~ server: port: 6002 spring: main: allow-bean-definition-overriding: true application: name: seata-B # --------------cloud配置------------------ cloud: nacos: discovery: server-addr: 127.0.0.1:8848 #注册服务控制中心 # 这里配置分组 alibaba: seata: tx-service-group: my_test_tx_group #------------数据库链接----------------------------- datasource: url: jdbc:mysql://****.mysql.rds.aliyuncs.com:3306/hj_mall?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull&useSSL=false username: root password: *** driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:/mapper/*Mapper.xml #实体扫描,多个package用逗号或者分号分隔 typeAliasesPackage: com.hjf.test.entity,com.hjf.base,MyOgnl configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射 map-underscore-to-camel-case: false global-config: db-config: id-type: auto #开启hystrix 熔断 feign: hystrix: enabled: true client: config: default: connect-timeout: 4000 read-timeout: 4000 # --------------负载均衡器配置------------------ ribbon: ReadTimeout: 60000 ConnectTimeout: 60000 #阿里分布式事务配置 seata: service: tx-service-group: my_test_tx_group vgroup-mapping: #这里的组名my_test_tx_group就是上面已经配置过的 # seata-server 对应的就是register.conf里的application选项的内容 my_test_tx_group: seata-server grouplist: #这里对应的就是上面的seata-server,后面的蚕食seata服务的IP地址和端口号 seata-server: 127.0.0.1:8091 enable-degrade: false disable-global-transaction: false ~~~ ***** **2、配置数据源代理和扫描器** ~~~ @Configuration public class DataSourceProxyConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } } ~~~ **3、在需要开启分布式事务的方法上加上@GlobalTransactional注解。** ~~~ 在TM开启全局事务之后,TC返回一个XID,RootContext类中会保存XID,该XID是一个线程变量。 RM中根据XID向TC注册分支变量。 ~~~ ***** ~~~ @GlobalTransactional(rollbackFor = Exception.class) public BaseResp addCarousel(MarketCarousel q) { marketCarouselMapper.insert(q); //远程调用微服务添加数据 BaseResp r=feignService.add(q); if (StringUtils.isBlank(q.getTitle())){ //抛出异常事务回滚 throw new NullPointerException("抛出异常........"); } return BaseResp.SUCCESS; } ~~~ ***** **4、在业务系统和各个微服务中加入undo\_log表。** ***** ~~~ CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) 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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ~~~