项目使用Spring Cloud 分布式框架,使用过程中,客户提出了购买物品时,调用库存微服务,使用库存后,调用支付微服务失败,库存数据未正常回滚,导致数据两边数据不一致的问题。因此决定引入阿里的分布式事务Seata。
本文边详细的介绍一下整合的过程,以及整合过程中遇到的问题。
选择对应的版本
可以在 Github上spring-cloud-alibaba的开源项目找到SpringCloud对应的seata版本
找到项目对应的版本后,下载对应的seata server,下载地址:https://github.com/seata/seata/releases
下载完对应的版本后, 将其解压。可以看到内容如下:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka" # 修改这里,这里我使用的时eureka注册中心,对应下面的eureka配置,其他的配置信息可删除
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
# 修改这里,因为上面用的是eureka,需要配置下面的eureka注册信息
eureka {
serviceUrl = "http://127.0.0.1:8761/eureka"
application = "seata-server"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
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" # 这里使用file的配置文件,其余的可以删除掉
nacos {
serverAddr = "localhost"
namespace = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
# 使用conf下的file.conf配置
file {
name = "file.conf"
}
}
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#vgroup->rgroup
vgroup_mapping.my_test_tx_group = "seata-server" # 修改这里,registry.conf文件里的eureka的application
#only support single node
default.grouplist = "127.0.0.1:8091" # 修改这里,这里是seata的默认端口号,启动的时候可以指定端口号,记得对应
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
report.retry.count = 5
tm.commit.retry.count = 1
tm.rollback.retry.count = 1
}
## transaction log store
store {
## store mode: file、db
mode = "file" # 这里使用的是file
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## database store
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://192.168.0.65:3306/core"
user = "root"
password = "123456"
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
lock {
## the lock store mode: local、remote
mode = "remote"
local {
## store locks in user's database
}
remote {
## store locks in the seata's server
}
}
recovery {
#schedule committing retry period in milliseconds
committing-retry-period = 1000
#schedule asyn committing retry period in milliseconds
asyn-committing-retry-period = 1000
#schedule rollbacking retry period in milliseconds
rollbacking-retry-period = 1000
#schedule timeout retry period in milliseconds
timeout-retry-period = 1000
}
transaction {
undo.data.validation = true
undo.log.serialization = "jackson"
undo.log.save.days = 7
#schedule delete expired undo_log in milliseconds
undo.log.delete.period = 86400000
undo.log.table = "undo_log"
}
## metrics settings
metrics {
enabled = false
registry-type = "compact"
# multi exporters use comma divided
exporter-list = "prometheus"
exporter-prometheus-port = 9898
}
support {
## spring
spring {
# auto proxy the DataSource bean
datasource.autoproxy = false
}
}
进入bin目录,如果使用的eureka,需要先启动eureka server注册中心,使用其他的,也需要先启动
linux下启动seata server: -p 后面可以指定seata server启动的端口号
nohup sh seata-server.sh -p 8091 &
启动后可以看到
windows启动直接双击。
启动后查看eureka是否注册到eureka注册中心。
在pom.xml的dependencyManagement下引用spring-cloud-alibaba
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-seataartifactId>
<version>1.5.1.RELEASEversion>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>1.5.1.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependencies>
dependencyManagement>
文件对应内容如下:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
eureka {
serviceUrl = "http://127.0.0.1:8761/eureka" # 注意这里,对应eureka注册中心
application = "seata-server" # 注意这里,对应registry.conf文件里的eureka的application
weight = "1"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
file {
name = "file.conf"
}
}
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#vgroup->rgroup
vgroup_mapping.my_test_tx_group = "seata-server" # 注意这里,对应registry.conf文件里的eureka的application
#only support single node
default.grouplist = "127.0.0.1:8761:8091" # 修改这里,对应seata server的ip和端口号
#degrade current not support
enableDegrade = false
#disable
disable = false
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
report.retry.count = 5
tm.commit.retry.count = 1
tm.rollback.retry.count = 1
}
transaction {
undo.data.validation = true
undo.log.serialization = "jackson"
undo.log.save.days = 7
#schedule delete expired undo_log in milliseconds
undo.log.delete.period = 86400000
undo.log.table = "undo_log"
}
support {
## spring
spring {
# auto proxy the DataSource bean
datasource.autoproxy = false
}
}
spring:
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group
在每个业务库中创建以下数据库表,表明可以修改,修改后记得修改对应的配置
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `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;
禁用Springboot的dataSources自动装配,在启动项添加以下配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
创建dataSources数据源控制:DataSourceConfig.java
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
// 配置mybatis-plus的分页
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
Interceptor[] plugins = {paginationInterceptor};
sqlSessionFactoryBean.setPlugins(plugins);
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
// 配置spring的本地事务
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
// 配置mybatis-plus的逻辑删除
sqlSessionFactoryBean.setGlobalConfig(new GlobalConfig().setSqlInjector(new LogicSqlInjector()));
// 配置mybatis-plus的log打印
MybatisConfiguration cfg = new MybatisConfiguration();
cfg.setJdbcTypeForNull(JdbcType.NULL);
cfg.setMapUnderscoreToCamelCase(true);
cfg.setCacheEnabled(false);
cfg.setLogImpl(StdOutImpl.class);
sqlSessionFactoryBean.setConfiguration(cfg);
return sqlSessionFactoryBean.getObject();
}
}
准备两个测试的微服务(cloud-iqs,cloud-usc),这里只是测试效果用,实际根据业务需求来。
编写controller:
@ApiOperation("test seata, flag为1,正常提交 为2:cloud-iqs失败, 为3:cloud-usc失败")
@GetMapping("/testSeata")
public R testSeata(int flag){
purOrderService.testSeata(flag);
return R.ok();
}
编写service:
// seata分布式无使用GlobalTransactional注解
@GlobalTransactional
@Override
public void testSeata(int flag) {
System.out.println(RootContext.getXID());
TransExpense transExpense = new TransExpense();
transExpense.setType("111");
transExpenseMapper.insert(transExpense);
// 通过fegin调用cloud-usc
R r = uscFeginClient.testSeata(flag);
if(r.getCode() != 200){
throw new RuntimeException(r.getMsg());
}
if(flag == 2){
throw new RuntimeException("cloud-iqs fail");
}
}
编写fegin:
@GetMapping(value = "user/testSeata", consumes = MediaType.ALL_VALUE)
R testSeata(@RequestParam("flag") int flag);
编写fegin调用的controller:
@ApiOperation(value = "testSeata")
@GetMapping(value = "testSeata")
public R testSeata(int flag){
System.out.println("----------------------------------------------------user");
User user = new User();
user.setAccount(new Date().toString());
user.setPassword("22222222222");
userService.save(user);
if(flag == 3){
throw new RuntimeException("cloud-usc fail");
}
return R.ok();
}
使用swagger2调用
查看数据库对应表结构:
usc库:
使用swagger2调用
查看数据库对应表结构:
usc库:
iqs库:
使用swagger2调用
查看数据库对应表结构:
usc库:
本文到此结束了,后续文章会陆续更新,文档会同步在CSDN和GitHub保持同步更新。有问题请留言
CSDN:https://blog.csdn.net/qq_34988304/category_8820134.html
Github文档:https://github.com/hack-feng/Java-Notes/tree/master/src/note/SpringCloud
GitHub源码:https://github.com/hack-feng/Spring-Cloud-Edgware.git