微服务OpenCloud中关于Seata处理分布式事务的建议
一、问题背景
现实微服务架构中,各个模块都是分数据库存储的。互相隔离,多数据源。而Open Cloud是基于Spring Cloud项目应运而生的,使用了阿里巴巴的Nacos作为微服务注册中心,随之发展而来的Spring Cloud Alibaba等产业链。
由于微服务各订单、各服务之间数据会出现一致性问题,事务不同步。传统的Spring AOP事务处理方式不能解决跨库跨项目引起的问题。为了解决这个问题,阿里先是推出了Rocket MQ消息中间件,带有解决分布式事务的方案。这几年,Seata是推出的另一款阿里的分布式事务解决方案,本文件谈谈Seata的主要功能和特点,并结合Open Cloud微服务Nacos注册中心来说明问题。
二、Seata
Seata作为分布式事务的整体解决方案,在解决方案中引用了3种角色:
事务协调器:用来维护事物的运行状态,负责协调并驱动全局事务的提交或回滚。
控制全局事务的边界:开启全局事务,通过发起最终全局提交或全局回滚的决议。
控制分支事务:用来作为分支注册、状态汇报,接收事务协调器的指令,驱动本地事务的提交或回滚。
其工作模式如下:
2.1Seata执行引擎
接下来介绍的是Seata的执行引擎:
(1)由TM推向TC截取开启一个GLobal Transaction,全局事务创建成功并生成一个全局唯一的XID.
(2)XID在微服务的链路调用中进行全局传播.
(3)RM通过处理分支事务,先解析这条SQL语句,Seata通过生成对应的UNDO_LOG记录来处理。
2.2Seata支持的微服务注册中心
Seata支持Zookeeper、Dubbo、Nacos、Apolo等注册中心,易用性广泛。
三、Open Cloud
Open Cloud作为一款开源微服务分布式框架架构,采用了Nacos作为微服务的注册中心,采用Spring Cloud Gateway网关作为动态路由,结合了Bus消息总线、定时任务中心、基于OAuth2+Spring Cloud Security的权限认证机制,很适合大众使用。
Open Cloud注定会流行起来,作为微服务架构的实践先驱。
四、本文采用的Seata解决方式
本文使用Seata的AT事务模式进行讲解。
AT模式有以下几个特点:
(1)支持本地的ACID机制,整体是通过2PC的传播。
(2)读写分离
4.1项目整合
本文采用的是Spring Boot 2.1.6+OpenCloud,注册中心是Nacos
引入Seata修改registry.conf
使用file.conf,选择db模式,更改数据库属性连接。
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = false
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThreadPrefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
## transaction log store, only used in server side
## global_table、branch_table和lock_table是需要建立的表,存储会话和锁信息,可以给server使用单独的数据库。
store {
## store mode: file、db
mode = "db"
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/seata"
user = "root"
password = "root"
minConn = 1
maxConn = 10
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
## server configuration, only used in server side
server {
recovery {
#schedule committing retry period in milliseconds
committingRetryPeriod = 1000
#schedule asyn committing retry period in milliseconds
asynCommittingRetryPeriod = 1000
#schedule rollbacking retry period in milliseconds
rollbackingRetryPeriod = 1000
#schedule timeout retry period in milliseconds
timeoutRetryPeriod = 1000
}
undo {
logSaveDays = 7
#schedule delete expired undo_log in milliseconds
logDeletePeriod = 86400000
}
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
maxCommitRetryTimeout = "-1"
maxRollbackRetryTimeout = "-1"
rollbackRetryTimeoutUnlockEnable = false
}
## metrics configuration, only used in server side
metrics {
enabled = false
registryType = "compact"
# multi exporters use comma divided
exporterList = "prometheus"
exporterPrometheusPort = 9898
}
创建以下几个表,建立业务场景:
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 `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;
紧接着启动seata
4.2项目引入Seata坐标
引入Seata与Spring Boot项目的坐标依赖:
io.seata
seata-spring-boot-starter
1.2.0
io.seata
seata-all
io.seata
seata-all
1.2.0
至此,项目中就引入了Seata的坐标工具类
4.3配置yml
在application-prod.yml中引入Seata开启配置信息
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: xxdi_tx_group
4.4配置数据源代理扫描器
配置数据源全局代理扫描工作类提供Seata全局事务扫描
@Configuration
public class OpenCloudSeataConfig {
@Value("${spring.application.name}")
private String applicationId;
// 第二个参数是file.conf中service.vgroupMapping.后面的值
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
return new GlobalTransactionScanner(applicationId, "xxdi_tx_group");
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource(DataSourceProperties properties) {
HikariDataSource hikariDataSource =
properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
return new DataSourceProxy(hikariDataSource);
}
4.5开启分布式事务
在需要开启分布式事务的代码上加上@GlobalTransactional注解
就开启了分布式事务,在DutyService中的示例,开启Feign调用,RoleService是另外一个微服务工程中的Feign服务
例如:
@GlobalTransactional(name="oc-test",rollbackFor=BaseException.class)
public Duty saveDuty(Duty duty)
{
// 输入本地事务
DutyDao dutyDao = new DutyDao();
dutyDao.insert(duty);
//Feign事务
Duty duty2 = roleService.sku("7799005533");
return duty2;
}
name取一事务名称,rollback指定回滚Exception
4.6开启Feign拦截器
在Feign调用过程中加入XID处理记录状态
@Component
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String xid = RootContext.getXID();
if (StringUtils.isNotBlank(xid)) {
template.header("xid", xid);
}
}
}
4.7各个子系统引入undo_log
子系统需要引入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;
五、总结
Seata为分布式事务提供了解决方案,适合于大型分布式系统,所有用户使用。