这是一段完整的配置spring cloud +feign+nacos 项目下,增加 seata 的 AT 模式作为的分布式事务解决方案,同时希望可以通过这个流程下来,
可以看到我在过程中参考的信息来源,去总结如何引入框架,同时存在疑问信息,从哪些现有信息里找到解决方案的方式有些帮助。
nacos:2.0 以上
seata:1.5.2
Releases · seata/seata · GitHub
下载找到Assets下的 seata-server 压缩包。
此处示例用的是 mysql,还可以支持 oracle 和 postgresql
这里有熟悉的,是不是很像 mysql 的undo_log 文件机制,seata 的实现其实也是模拟 mysql 的 undo_log 的实现方案去做到回滚的。
但是目前 seata 回滚的 sql 还是存在一些限制,详情可查阅: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;
参考的配置方式,来源于同级目录下 application.example.yml
里面针对各个类型的配置示例,都提供出来,包括以下的三点,
既然用到 nacos 和 mysql,那么配置中心、注册中心、store 干脆都使用上。
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
dataId: "seataServer.properties"
username: "nacos"
password: "nacos"
参考:nacos
registry:
type: nacos
nacos:
##注册的服务名称
application: seata-server
##nacos的地址端口
server-addr: 127.0.0.1:8848
##此处注意自己的业务服务的 group 和 namespace 配置,匹配得到才能访问,这里仅仅举例
group : "SEATA_GROUP"
namespace: ""
#对应的账号密码
username: "nacos"
password: "nacos"
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: mysql
password: mysql
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
参考官方文档:nacos
sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password
//localhost -p 8848 --> nacos 的地址
//-g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca ---> 对应前面配置 nacos 的注册中心的 group 和 namespace,namespace 需要用业务服务的,若用的 public ,-t 这部分可删除
// -u username -w password -->username:nacos 账号,password:nacos 密码
sh ./script/config-center/nacos/nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u nacos -w nacos
(seata 和业务服务均需要在这个初始化完成后再启动)
在对应nacos 创建一个 seataServer.properties 将配置信息,黏贴进去。
3、业务服务的配置
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.5.2</version>
</dependency>
依赖关联回滚的服务添加依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: "SEATA_GROUP"
dataId: "seataServer.properties"
username: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
##注册的服务名称
application: seata-server
##nacos的地址端口
server-addr: 127.0.0.1:8848
##此处注意自己的业务服务的 group 和 namespace 配置,匹配得到才能访问,这里仅仅举例
group : "SEATA_GROUP"
##对应的账号密码
username: "nacos"
password: "nacos"
若是配置为 dataId: "seataServer.properties",那么配置文件走的是这里,seata 的 application.yal 关于 mode 、store、决定生效取决于配置文件
通过全局配置,增加服务通信信息中,可以携带 seata 的 xid 进行传输
@Configuration
public class FeignSupportConfig {
/**
* feign请求拦截器
*
* @return
*/
@Bean
public RequestInterceptor requestInterceptor(){
return new FeignBasicAuthRequestInterceptor();
}
}
@Slf4j
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(AsapConstants.DATA_SOURCE_CODE, AsapConstants.DATA_SOURCE);
requestTemplate.header(AsapConstants.REQUEST_ID, TraceIdUtils.getTraceId());
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (ObjectUtils.isEmpty(requestAttributes)){
return ;
}
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
//服务通信携带初始请求的 header
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
// 跳过 content-length
if ("content-length".equals(name)){
continue;
}
requestTemplate.header(name, values);
}
//传递 seata 回滚 xid
String xid = RootContext.getXID();
requestTemplate.header(RootContext.KEY_XID, xid);
} else {
log.info("feign interceptor error header:{}", requestTemplate);
}
}
启动成功后观察,启动的 seata web 服务时 7091 端口。 访问127.0.0.1:7091
业务服务启动后,结合 @GlobalTransactional 注解验证测试有无效果。
(1)服务部署过程中,seataServer.properties 的生效优先于seata的 config 文件application.yml ,过程中修改数据库的信息,仅仅修改这里不生效。
(2)由于项目中存在全局异常的处理,所以导致明明抛出异常但是事务还是不会回滚。
看 seata 的代码,response 的状态码异常才会 触发回滚。
(1)xid 在服务中传输存在问题,打日志检查,处理方式见第 3(2)。
(2)feign 的 db 没脸上,事务范围内,执行过程中在执行 sql 后打上 断点,然后登录seata 的管理台,检查是否存在事务和全局锁信息,有刚刚执行 Sql 的锁信息。
(3)全局异常导致失效,可以通过全局异常捕获过滤这些接口,或是通过手动执行事务的方式触发回滚。
try {
TransactionManager manager = TransactionManagerHolder.get();
manager.rollback(RootContext.getXID());
}catch (Exception e){
log.error("手动回滚全局异常失败:{}",e);
}