分布式事务补充-SEATA的相关说明

# SEATA ## SEATA是什么 ![image.png](https://cos.easydoc.net/13568421/files/lmcqze63.png) ![image.png](https://cos.easydoc.net/13568421/files/lmcr1agr) ### 结构说明 - RM: 资源管理器, 资源管理器的作用主要是操作数据库资源, 并加入全局的分布式事务, 形成一个事务分支, 并且具有回滚的作用 - TM: 事务管理器, 事务管理器的作用主要是开启一个全局的分布式事务 - TC: 事务协调者, 事务协调者的作用主要是对事务进行协调通知的作用, 如TM要开启一个全局的分布式事务时, TC会通知其他分事务, 也会通知各个分支事务的回滚 ### 执行流程的概述 > 由一个TM开启全局的分布式事务, 并把消息发送给TC, TC通过其他的RM加入分布式事务, RM获取消息后发消息给TC加入分布式事务 > 当事务结束后, TM主导分布式事务的提交或回滚操作, 一旦做出决定, 将决定交予TC分发给其他分支事务, 保证数据一致性 ## 其他知识 ### undo_log表相较于TCC事务补偿方案的好处 > undo_log日志表会自动将事务提交前的状态保存下来, 进行回滚的时候, 就不需要手动的反向补充, 可用根据回滚日志表进行反向补偿, 我们无需介入事务的反向补偿 ### 什么是反向补偿 > 反向补偿的目的是为了让事务回滚, 可以理解为恢复原来的状态, 添加即删除, 删除即添加, 修改即修改回去 ## SEATA的架构 1. 创建回滚日志表(所有的业务数据库) ```sql 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; ``` 2. 下载SEATA(没把握自己能适配项目的, 请下载[这个地址下的版本](https://github.com/seata/seata/releases/tag/v0.7.1)) 3. POM ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> </dependency> ``` 4. 修改file.conf ```conf db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" url = "jdbc:mysql://192.168.10.131:3306/seata" user = "root" password = "root" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } ``` 5. 修改register.conf ```conf registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" ``` 6. 创建相关的数据库与数据表 ```sql CREATE DATABASE `seata` CHARACTER SET 'utf8'; -- the table to store GlobalSession data drop table if exists `global_table`; create table `global_table` ( `xid` varchar(128) not null, `transaction_id` bigint, `status` tinyint not null, `application_id` varchar(32), `transaction_service_group` varchar(32), `transaction_name` varchar(64), `timeout` int, `begin_time` bigint, `application_data` varchar(2000), `gmt_create` datetime, `gmt_modified` datetime, primary key (`xid`), key `idx_gmt_modified_status` (`gmt_modified`, `status`), key `idx_transaction_id` (`transaction_id`) ); -- the table to store BranchSession data drop table if exists `branch_table`; create table `branch_table` ( `branch_id` bigint not null, `xid` varchar(128) not null, `transaction_id` bigint , `resource_group_id` varchar(32), `resource_id` varchar(256) , `lock_key` varchar(128) , `branch_type` varchar(8) , `status` tinyint, `client_id` varchar(64), `application_data` varchar(2000), `gmt_create` datetime, `gmt_modified` datetime, primary key (`branch_id`), key `idx_xid` (`xid`) ); -- the table to store lock data drop table if exists `lock_table`; create table `lock_table` ( `row_key` varchar(128) not null, `xid` varchar(96), `transaction_id` long , `branch_id` long, `resource_id` varchar(256) , `table_name` varchar(32) , `pk` varchar(32) , `gmt_create` datetime , `gmt_modified` datetime, primary key(`row_key`) ); ``` 7. 启动SEATA, 并在nacos验证seata是否可以成功注册 8. 配置数据源 ```java @Configuration public class SeataConfig { @Bean public DataSource dataSource(DataSourceProperties dataSourceProperties) { HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); if (StringUtils.hasText(dataSourceProperties.getName())) { dataSource.setPoolName(dataSourceProperties.getName()); } return new DataSourceProxy(dataSource); } } ``` 9. 拷贝配置文件 ![image.png](https://cos.easydoc.net/13568421/files/lmd92wfm.png) ### 为什么@GlobalTransactional只有需要一个 > 因为由架构图可知, TM只需要一个, TM一个就可以开启全局分布式事务, 而@GlobalTransactional就是TM, 因此只需要一个 ## Seata的实战 ![image.png](https://cos.easydoc.net/13568421/files/lmdg2yo3.png) ![image.png](https://cos.easydoc.net/13568421/files/lmdg3b2m.png) ### Bug修复 #### 1. 找不到事务分组 ```java 2023-09-10 17:43:20.156 WARN 51996 --- [nfigOperate_1_2] io.seata.config.FileConfiguration : Could not found property service.vgroup_mapping.bitmall-order-fescar-service-group, try to use default value instead. 2023-09-10 17:43:20.156 ERROR 51996 --- [imeoutChecker_1] i.s.c.r.netty.NettyClientChannelManager : no available server to connect. ``` > 找不到事务分组, 此刻需要在yaml中配置事务分组即可 ```yml cloud: alibaba: seata: tx-service-group: bitmall_order_tx_group ``` #### 2. seata的不支持问题 ```java ### Error updating database. Cause: io.seata.common.exception.NotSupportYetException ### The error may exist in com/junjie/bitmall/order/dao/OrderItemDao.java (best guess) ### The error may involve com.junjie.bitmall.order.dao.OrderItemDao.insert-Inline ### The error occurred while setting parameters ### SQL: INSERT INTO oms_order_item ( sku_attrs_vals, spu_name, integration_amount, order_sn, sku_price, spu_pic, gift_integration, real_amount, sku_quantity, sku_name, spu_brand, coupon_amount, sku_pic, spu_id, gift_growth, promotion_amount, sku_id, category_id ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ### Cause: io.seata.common.exception.NotSupportYetException ``` > **这里说明, 0.7.1版本的seata是不支持数据的批量操作的** > 解决方案: 要么不要用批量操作, 一个一个手动插入, 要么换更高版本的seata > 由于没有找到适合的版本号, 建议临时将批量操作换成多次操作 #### 3. 全局事务不起效的原因 > **如果涉及到全局分布式事务的微服务都需要配置seata, 这里全局分布式事务没有起效的原因是ware微服务没有加入seata** > 本质原因是另一个微服务无法加入事务分组, 因此无法回滚 > 注意, 如果远程微服务catch了异常也没关系, 因此有一个RM回滚了, 其他RM或TM也会自动回滚, 因此不需要特别注意 ## Seata绝唱 ### 高并发场景为什么不用Seata [官网](https://seata.io/zh-cn/docs/overview/what-is-seata) > 由于官网可知, seata进行了读隔离和写隔离, 具有锁的操作, 锁会带来串行化问题, 即限制了高并发, 因此, <font color="red">**高并发场景下不能用seata**</font>