seata集成
本文描述了数字化运营平台分布式事务seata集成部署与集成,预期读者为运维人员,开发人员。
相关人员在进行使用时,必须先具备springboot,mysql,分布式事务相关知识。
Seata 是一款开源的分布式事务解决方案。Seata可集成nacos配置。
Seata0.8.0版本。
https://seata.io/zh-cn/blog/download.html
Ø 每个微服务数据库执行undo_log表脚本
Ø db_store.sql数据库脚本
配置seata-server\conf\ registry.conf的nacos地址
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
serverAddr = "10.0.4.79:8848"
namespace = "public"
cluster = "default"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "10.0.4.79:8848"
namespace = "public"
cluster = "default"
}
}
è 注意:ip :port,ip :port格式
配置seata-server\conf\ file.conf的seate启动地址列表
service {
#vgroup->rgroup
vgroup_mapping.my_test_tx_group = "default"
#only support single node
default.grouplist = "10.0.4.79:8091"
#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"
}
è 注意:ip :port,ip :port格式
配置seata-server\conf\ nacos-config.txt的参数
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.thread-factory.boss-thread-prefix=NettyBoss
transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
transport.thread-factory.share-boss-worker=false
transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
transport.thread-factory.client-selector-thread-size=1
transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
transport.thread-factory.boss-thread-size=1
transport.thread-factory.worker-thread-size=8
transport.shutdown.wait=3
service.vgroup_mapping.module-service-consumer-seata=default
service.vgroup_mapping.module-service-provider-seata=default
service.enableDegrade=false
service.disable=false
service.max.commit.retry.timeout=-1
service.max.rollback.retry.timeout=-1
client.async.commit.buffer.limit=10000
client.lock.retry.internal=10
client.lock.retry.times=30
store.mode=db
store.file.dir=file_store/data
store.file.max-branch-session-size=16384
store.file.max-global-session-size=512
store.file.file-write-buffer-cache-size=16384
store.file.flush-disk-mode=async
store.file.session.reload.read_size=100
store.db.datasource=dbcp
store.db.db-type=mysql
store.db.driver-class-name=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://10.0.4.79:13306/seata?useUnicode=true
store.db.user=root
store.db.password=yda#121@11
store.db.min-conn=1
store.db.max-conn=3
store.db.global.table=global_table
store.db.branch.table=branch_table
store.db.query-limit=100
store.db.lock-table=lock_table
recovery.committing-retry-period=1000
recovery.asyn-committing-retry-period=1000
recovery.rollbacking-retry-period=1000
recovery.timeout-retry-period=1000
transaction.undo.data.validation=true
transaction.undo.log.serialization=jackson
transaction.undo.log.save.days=7
transaction.undo.log.delete.period=86400000
transaction.undo.log.table=undo_log
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registry-type=compact
metrics.exporter-list=prometheus
metrics.exporter-prometheus-port=9898
è 注意:service.vgroup_mapping.xxx=default中的xxx为springboot程序的seata.tx-service-group的value
执行seata-server\conf\ nacos-config.sh的参数
./nacos-config.sh 10.0.4.79
è 注意:./nacos-config.sh nacos的ip:port 格式
可以查看nacos的生产配置
è 注意:检查nacos配置中的store.db.password是正确
执行seata-server\conf\的db_store.sql,db_undo_log.sql
Ø 每个微服务的数据库(ocss)执行undo_log表脚本
Ø db_store.sql数据库脚本
è 注意: 每个微服务数据库执行undo_log表脚本
执行seata-server\bin\ seata-server.sh
nohup sh ./seata-server.sh -p 8091 -h 10.0.4.79 -m db -n 1 >log.out 2>1 &
è 注意:-p端口,-h地址
执行java -jar 命令,如下
java -jar module-service-consumer-6.0.0-SNAPSHOT.jar --spring.cloud.nacos.config.server-addr=10.0.4.79:8848
è 注意:通过spring.cloud.nacos.config.server-addr自动配置了seata的nacos地址
参考工程template\
module-service-consumer
module-service-consumer
检查pom.xml或者依赖的consumer-dependence,provider-dependence的pom.xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.8.0</version>
</dependency>
è 注意:注意必须是0.8.0版本
检查/com/yunbiaokj/module/service/provider/config/DruidConfig.java
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Primary
@Bean
public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSourceProxy);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// bean.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));
bean.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml"));
SqlSessionFactory factory = null;
try {
factory = bean.getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
return factory;
}
è 注意:注意mapper文件必须能扫描到
检查/src/main/resources/ registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
#serverAddr = "10.0.4.79:8848"
namespace = "public"
cluster = "default"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
#serverAddr = "10.0.4.79:8848"
namespace = "public"
cluster = "default"
}
}
è 注意:#serverAddr必须注释掉
配置/src/main/resources/ bootstrap.properties的seata.tx-service-group
spring.cloud.alibaba.seata.tx-service-group=module-service-consumer-seata
è 注意:配置seata.tx-service-group
配置/src/main/resources/ bootstrap.properties的seata.tx-service-group
è 注意:配置dataId的service.vgroup_mapping.xxx中的xxx为bootstrap.properties的seata.tx-service-group的值
检查com/yunbiaokj/module/service/provider/ServiceProviderApplication.java
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.yunbiaokj.module.service.consumer", "com.yunbiaokj.common.mq", "com.yunbiaokj.common.session"})
public class ServiceConsumerApplication {
public static void main(String[] args) throws IOException {
initCommand( args);
SpringApplication.run(ServiceConsumerApplication.class, args);
}
private static void initCommand(String[] args) throws IOException{
setProperty(args,"serverAddr","spring.cloud.nacos.config.server-addr");
setProperty(args,"namespace","spring.cloud.nacos.discovery.namespace");
}
private static void setProperty(String[] args,String propertyKey,String commandKey) throws IOException{
Optional<String> serverAddrValueOptional = Arrays.stream(args).filter(str -> str.contains(commandKey) ).findFirst();
System.setProperty(propertyKey, serverAddrValueOptional.isPresent()?serverAddrValueOptional.get().split("=")[1]:getPropertiesValue(commandKey));
}
private static String getPropertiesValue(String key) throws IOException{
return PropertiesLoaderUtils.loadAllProperties("bootstrap.properties").getProperty(key);
}
}
}
è 注意:注意必须通过initCommand()方法进行初始化
检查消费者添加GlobalTransactional注解
/com/yunbiaokj/module/service/consumer/controller/SeataController.java例子如下
@RequestMapping(value = "/module/service")
@RestController
public class SeataController {
@Autowired
private RestTemplate restTemplate;
@GlobalTransactional(timeoutMills = 300000, name = "SeataController-seata")
@Transactional(rollbackFor = Exception.class)
@RequestMapping(value = "/seata/{id}", method = RequestMethod.GET)
public String seata(@PathVariable Long id) {
String r1 = restTemplate.getForObject("http://module-service-provider/module/seata/del1/"+(id-1) ,
String.class);
System.out.println("r1 " +r1 );
String r2 =restTemplate.getForObject("http://module-service-provider/module/seata/del2/"+id ,
String.class);
System.out.println("r2 " +r2 );
return r1 +" " + r2;
}
}
è 注意:注意必须GlobalTransactional注解
检查消费者添加RootContext.getXID()
/com/yunbiaokj/module/service/provider/controller/SeataController.java例子如下
@RequestMapping(value = "/module/seata")
@RestController
public class SeataController {
@Autowired
private HotelService hotelService;
//@Transactional // 开启新事物
@GetMapping("/del1/{id}")
public String seata(@PathVariable(name="id") Long id) throws Exception {
System.out.println("[seata] 当前 XID: "+ RootContext.getXID());
System.out.println("SeataController seata id " +id);
//return hotelService.selectByCityId(id);
if (hotelService.selectByCityId(id)==null) {
throw new Exception("数据不存在");
}
hotelService.deleteByCityId(id);
return id +" is delete";
}
//@Transactional // 开启新事物
@GetMapping("/del2/{id}")
public String seata2(@PathVariable(name="id") Long id) throws Exception{
System.out.println("[seata2] 当前 XID: "+ RootContext.getXID());
System.out.println("SeataController seata2 id " +id);
if (hotelService.selectByCityId(id)==null) {
throw new Exception("数据不存在");
}
hotelService.deleteByCityId(id);
return id +" is delete";
}
}
è 注意:注意消费者能否获取RootContext.getXID(),并且能抛出throws Exception才能实现分布式事务