@Transactional注解解读
◆@Transactional 是Spring 事务管理提供的注解,在一个方法中加上了这个注解,那么这个方法就将是有事务的,方法内的操作要么一起提交、要么一起回滚。
Propagation.REQUIRED(默认传播行为):支持当前事务;如果当前没有事务,则新建一个事务
Propagation.REQUIRES_NEW :新建事务;如果当前存在事务,则把当前事务挂起
Propagation.SUPPORTS :支持当前事务;否则将以非事务方式执行
Propagation.MANDATORY :支持当前事务 ;否则将抛出IllegalTransactionStateException异常
Propagation.NOT_SUPPORTED :不支持当前事务,而是始终以非事务的方式执行
Propagation.NEVER:以非事务方式执行;如果当前存在事务,则抛出IlegalTransactionStateException 异常
Propagation.NESTED :如果当前存在事务,则对于该传播行为修饰的方法会依然使用当前事务
◆把注解标注在非public修饰的方法上
◆propagation (传播行为)属性配置错误(不合理)
◆rollbackFor 属性设置错误
◆在同一个类中方法调用,导致事务失效
◆自己主动去catch ,代表「没有出现」异常,导致事务失效
◆数据库引擎本身就不支持事务(例如MyISAM) ,当然也不会生效
分布式事务是来源于微服务的(或类似的场景) ,服务之间存在着调用,且整个调用链路上存在着多处(分布在不同的微服务上)写数据表的行为,那么,分布式事务就要保证这些操作要么全部成功,要么全部失败。
1.强一致性:任何一次读都能读到某个数据的最近一次写的数据(要求最高)
2.弱一致性:数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性(绝大多数的业务场景都不允许)
3.最终一致性:不保证在任意时刻数据都是完整的(状态一致) ,但是,随时时间的推移(会有个度量) , 数据总是会达到一致的状态
◆两阶段指的是分两步提交;存在一个中央协调器负责协调各 个分支事务
◆该方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行
Seata 中的三个重要角色: TC、TM、RM:
TM :事务的发起者,用于通知TC ,全局事务的开始、提交、回滚
RM:事务资源,每-个RM都会作为一个分支事务注册在TC .上
TC :事务协调者,也就是中央协调器;用于接收事务的注册、提交、回滚
Seata下载地址:下载中心
Seata目录结构
◆启动命令: nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m file &
-h :指定在注册中心注册的IP ;不指定时获取当前的IP ,外部访问部署在云环境和容器中的server建议指定
-p :端口号;默认端口号是8091
-m :事务日志存储方式,支持file、db、redis ,默认为file
需要修改配置文件file.conf,配置数据库的连接
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
## redis store property
redis {
host = "127.0.0.1"
port = "6379"
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
◆启动命令: nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 &
-n :用于指定seata-server节点ID ;如1、2、3...默认为1
注意:需要在MySQL中创建库和表
CREATE DATABASE `seata`;
CREATE TABLE IF NOT EXISTS `seata`.`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(128),
`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`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `seata`.`branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`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`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `seata`.`lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
集群Seata Server ,高可用模式
需要修改配置文件file.conf,配置数据库的连接
同上
需要修改配置文件registry.conf,配置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 = "1bc13fd5-843b-4ac0-aa55-695c25bc0ac6"
cluster = "default"
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "1bc13fd5-843b-4ac0-aa55-695c25bc0ac6"
group = "SEATA_GROUP"
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
启动命令:
nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 &
nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8092 -m db -n 2 &
nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8093 -m db -n 3 &
◆pom.xml 中引入依赖: spring-cloud-starter alibaba-seata, HikariCP
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
com.zaxxer
HikariCP
true
◆创建undo_ log 表(如果业务使用了多个数据库,每一个数据库都要有这张表)
CREATE TABLE IF NOT EXISTS `ecommerce`.`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;
◆配置事务分组(配置文件file.conf,registry.conf)
file.conf
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
## redis store property
redis {
host = "127.0.0.1"
port = "6379"
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
##事务分组
service {
vgroupMapping.imooc-ecommerce = "default"
default.grouplist = "127.0.0.1:8091"
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
}
registry.conf
registry {
# file、nacos、eureka、redis、zk、consul
type = "file"
file {
name = "file.conf"
}
}
config {
type = "file"
file {
name = "file.conf"
}
}
bootstrap.yml
spring:
cloud:
alibaba:
seata:
tx-service-group: imooc-ecommerce # seata 全局事务分组
◆配置Seata数据源代理(思考下,为什么需要这个? )
import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* Seata 所需要的数据源代理配置类
* */
@Configuration
public class DataSourceProxyAutoConfiguration {
private final DataSourceProperties dataSourceProperties;
public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
/**
* 配置数据源代理, 用于 Seata 全局事务回滚
* before image + after image -> undo_log
* */
@Primary
@Bean("dataSource")
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dataSourceProperties.getUrl());
dataSource.setUsername(dataSourceProperties.getUsername());
dataSource.setPassword(dataSourceProperties.getPassword());
dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
return new DataSourceProxy(dataSource);
}
}
◆加载拦截器SeataHandlerInterceptor,实现微服务之间xid的传递
/**
* 添加拦截器配置
* */
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 添加用户身份统一登录拦截的拦截器
registry.addInterceptor(new LoginUserInfoInterceptor())
.addPathPatterns("/**").order(0);
// Seata 传递 xid 事务 id 给其他的微服务
// 只有这样, 其他的服务才会写 undo_log, 才能够实现回滚
registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**");
}
◆>将@GlobalTransactional注解标注在需要分布式事务的方法上