SpringCloud+Seata+MybatisPlus多数据源@GlobalTransactional异常数据未回滚的解决方案
一、问题解析
造成分布式事务失效,数据回滚异常的情况会有很多,下面介绍两种我遇到的情况,与对应的解决方案。
1.1 全局异常捕获吞异常导致分布式事务失效
一般的微服务都会使用@RestControllerAdvice
全局异常捕获对接口出现的异常进行捕获,包装成统一的数据结构响应给前端。
如果A服务
通过远程调用
到B服务
,A服务
作为全局事务的入口,当B服务
内部出现异常,被B服务的全局异常
捕获到返回,A服务
得到的响应结果并不是一个Exception
,可能是一个经过B服务包装的JSON对象{"code": 500,"msg":"method error"}
。
此时A服务会
认为B服务
的异常已经被处理
,异常没有被A服务的全局事务感知到,所以导致全局事务未能回滚
。
1.2 MybatisPlus多数据源dynamic-datasource-spring-boot-starter导致事务失效
当服务中使用了MybatisPlus
多数据源dynamic-datasource-spring-boot-starter
,如果没有正确的配置,可能也会出现事务失效的问题。
二、解决方案
2.1 全局异常事务失效解决
官方的常见问题解决文档中关于AT模式使用注意事项:常见问题
Q: 20. 使用 AT 模式需要的注意事项有哪些 ?
A:
- 必须使用代理数据源,有 3 种形式可以代理数据源:
- 依赖 seata-spring-boot-starter 时,自动代理数据源,无需额外处理。
- 依赖 seata-all 时,使用 @EnableAutoDataSourceProxy (since 1.1.0) 注解,注解参数可选择 jdk 代理或者 cglib 代理。
- 依赖 seata-all 时,也可以手动使用 DatasourceProxy 来包装 DataSource。
- 配置 GlobalTransactionScanner,使用 seata-all 时需要手动配置,使用 seata-spring-boot-starter 时无需额外处理。
- 业务表中必须包含单列主键,若存在复合主键,请参考问题 13 。
- 每个业务库中必须包含 undo_log 表,若与分库分表组件联用,分库不分表。
- 跨微服务链路的事务需要对相应 RPC 框架支持,目前 seata-all 中已经支持:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的支持,请大家引用 spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请结合 API 自行支持。
- 目前AT模式支持的数据库有:MySQL、Oracle、PostgreSQL和 TiDB。
- 使用注解开启分布式事务时,若默认服务 provider 端加入 consumer 端的事务,provider 可不标注注解。但是,provider 同样需要相应的依赖和配置,仅可省略注解。
- 使用注解开启分布式事务时,若要求事务回滚,必须将异常抛出到事务的发起方,被事务发起方的 @GlobalTransactional 注解感知到。provide 直接抛出异常 或 定义错误码由 consumer 判断再抛出异常。
方案一:官方的解决:
通过AOP动态创建/关闭Seata分布式事务
方案二:
在A服务
的FeignClient
的service上增加AOP切面
,A服务远程调用B服务,B服务内部异常
,在@AfterReturning
切点中判断B服务接口的响应内容
,判断响应码
为特定值时,可以直接抛出异常
,让全局事务感知
,也可以调用seata
的回滚API
进行事务回滚GlobalTransactionContext.reload(RootContext.getXID()).rollback();
2.2 多数据源导致全局事务失效解决
使用多数据源的情况下每个数据源都需要用seata进行代理,
spring.datasource.dynamic.seata: true
可以解决全局事务失效问题。seata.enable-auto-data-source-proxy: false
可以解决事务执行完毕,undo_log表中日志未清除的问题。