分布式事务补充-SEATA的相关说明
# SEATA
## SEATA是什么


### 结构说明
- 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. 拷贝配置文件

### 为什么@GlobalTransactional只有需要一个
> 因为由架构图可知, TM只需要一个, TM一个就可以开启全局分布式事务, 而@GlobalTransactional就是TM, 因此只需要一个
## Seata的实战


### 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>