微服务中 Seata “分支事务不回滚”问题的复盘

这篇具有很好参考价值的文章主要介绍了微服务中 Seata “分支事务不回滚”问题的复盘。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本篇记录原写于去年。

背景

一个下单逻辑跨了3个服务,采用 Seata AT 模式做分布式事务。

发现问题

分布式事务的处理并未成功,具体表现为:在出现异常后,3 个数据库里的表谁也没回滚。

本来以为是自己看错了,但是经过笔者的多次验证后,得到的结果都是如此,分支事务并未被正常处理。

好的,发现问题后该怎么办呢?

尝试解决问题

“小问题,轻轻松松~”

刚开始看到这个问题,笔者并没有觉得是个大问题,此时我并没有意识到严重性,也可以说是“轻敌”了。

当时,主要觉得问题可能出现配置和整合步骤上,于是做了如下4个事情:

  • 检查整合步骤,是不是漏掉了哪个步骤,或者少了哪些配置,亦或者是哪个配置项因为粗心没配置好。
  • 数据库中的表是否有问题。
  • 项目重启(遇事不决,重启试试)。
  • 查看日志,确认与Seata是否连接通信,是否正常注册TM、RM等。

由于觉得是小问题,所以从下午4点左右到6点,一直在做上述的4个事情。

结果就是,没发现步骤问题,也没发现配置问题,与Seata Server也正常通信,但是“分支事务不回滚”的问题还在。这个时候我其实有点意识到这可能不是个小问题了,有点小慌。但是搞了一段时间没头绪,脑子也乱了索性就下班回家了。

“坏了!见鬼了!”

本来想着第二天上班再处理的,但是被这个问题搞得实在睡不着,晚上11点半打开电脑继续处理。处理的过程和下午一样,我还是觉得可能是哪里不小心漏掉了步骤或者配置不对,扩大了检查范围,除了检查代码中的配置、数据库,还检查了Nacos Server的配置、Seata Server的配置,结果发现配置没问题,也没有遗漏什么。

为什么我觉得一开始是个小问题,然后主要是在检查配置项之类的。其原因就是之前在上一版已经整合过、配置过,而且验证通过,源码也没问题,分布式事务的处理结果是正确的。因此,我肯定会觉得只要整合步骤没有遗漏、配置项正确,分布式事务肯定会被正常处理。同样的代码、同样的配置、同样的测试环境,一个正常,一个不正常,这有点出乎意料了。这个时候我意识到了问题的严重性了,我觉得可能是遇见鬼了。确切地说,当时有点麻了,一份代码中分布式事务正常处理,一份完全没反应,说不麻是假的。

凌晨了,换个思路吧。

冷静下来后,我也不骗自己了,这代码肯定是有问题的,不然分支事务怎么会不回滚。但是我确实不知道问题在哪,怎么同样的东西在这一版项目里就不能用了呢?

不过,再去查配置、对比代码已经没意义了。既然确认代码有问题(不嘴硬了),那就开始根据Seata运行流程查一下哪里出了问题吧,主要是根据微服务实例的运行日志和Seata Server的运行日志来查的。

  • 与Seata Server通信正常。
  • 三个微服务实例都正常注册TM、RM等。
  • 全局事务正常开启。
  • 两个分支事务开启的日志一行都没出现。

不管是微服务实例的运行日志和Seata Server的运行日志,都没有看到两个分支事务的开启和处理,是的,没有任何信息和踪迹。再去数据库中确认了一下,undo_log表中也并没有数据。虽然不知道哪里出了问题,但是至少有方向了。

全局事务能够正常开启和回滚,而两个分支事务不正常(与Seata Server正常通信,但是都没有生效)。到这里已经大致有了眉目,乘客微服务和订单微服务两个服务实例的运行日志和Seata Server的运行日志,都没有看到任何关于全局事务的信息,这也说明了两个分支事务可能根本就没有注册成功。全局事务正常开启和处理,而两个本应出现的分支事务没有出现,它们之间“失联”了。

从代码层面来说,全局事务和分支事务的联系主要在一个变量上,这个变量就是全局事务的ID——xid。现在它们“失联”了,只能通过这个变量的产生、传递、接收、处理等几个步骤来确认问题在哪里了。

此时的要检查的内容就确定了下来:

  • 全局事务是否正常开启?xid是否正确地生成了?
  • xid是否正确地传递给下游的调用实例中?
  • 下游的调用实例是否正确地接到了xid?
  • 接到xid后是否正确处理并且开启分支事务?

“问题不清晰,看源码分析”。

为了确认上述的几个检查内容,还是要用debug模式看一看Seata处理分布式事务过程中所涉及到的源码,由于牵涉的源码太多,这里笔者挑几个重要节点介绍一下。

对于“全局事务是否正常开启?xid是否正确地生成了?”,主要跟进了下方两个类的源码:

io.seata.spring.annotation.GlobalTransactionalInterceptor.java

io.seata.tm.api.TransactionalTemplate.java

这两个类主要涉及全局事务的开启和处理,感兴趣的读者可以仔细地去探索一下。当然,结果是这个步骤并没有问题,全局事务正常开启,xid正确生成。

难道是xid生成了却没有传递给下游?对于这个问题,笔者主要在debug模式下跟进了com.alibaba.cloud.seata.feign.SeataFeignClient.java这个类的源码:

@Override
public Response execute(Request request, Request.Options options) throws IOException {

    Request modifiedRequest = getModifyRequest(request);
    return this.delegate.execute(modifiedRequest, options);
}

private Request getModifyRequest(Request request) {

    String xid = RootContext.getXID();

    if (StringUtils.isEmpty(xid)) {
        return request;
    }

    Map<String, Collection<String>> headers = new HashMap<>(MAP_SIZE);
    headers.putAll(request.headers());

    List<String> seataXid = new ArrayList<>();
    seataXid.add(xid);
    // 把xid放入请求头中
    headers.put(RootContext.KEY_XID, seataXid);

    return Request.create(request.httpMethod(), request.url(), headers, request.body(),
                          request.charset(), null);
}

向下游微服务实例发送请求时是由 SeataFeignClient 来完成的,在这个类中会对 Request 对象进一步包装,把 xid 放进请求的 header 参数中并传递给下游方法。即在 saverOrder() 方法中使用 OpenFeign 调用乘客微服务和订单微服务中的方法前会对 Request 做进一步的包装然后才发起请求。当然,结果是这个步骤并没有问题,xid 被放入header 参数中并传递给下游了。

在找问题的过程中,笔者还在订单服务的方法中添加了 request 参数,主要是为了查看该对象中是否有 xid 参数,如下所示:

    @DeleteMapping("/xxx")
    public Result<Boolean> deleteItemIds(@RequestParam("Ids") List<Long> Ids, HttpServletRequest request) {
        System.out.println("RootContext.getXID()="+ RootContext.getXID());
        //...
    }

在debug 模式下看了 request 对象中的内容,最终也是确认了 header 参数中是有 xid 参数的,也进一步确认了上游微服务实例(订单微服务)是正确地把 xid 传递到下游微服务中了,而且下游微服务实例也接收了 xid 参数,证明接收也没问题。

xid 的产生、传递、接收都没问题。到这里又卡住了,几个步骤好像都正常,怎么可能?怎么可能!怎么可能!!我还是有些不敢相信这个结果,如果这些步骤都正常的话,全局事务和分支事务怎么会“失联”呢?

“柳暗花明又一村。”

于是我赶紧在代码中又加上了打印 RootContext.getXID() 的语句,如果正确接收到上游微服务实例中传递的 xid 的话,这个变量肯定不会有问题。重新启动项目并请求 /saveOrder 验证整个分布式事务流程,打印RootContext.getXID() 的结果是 null,证明下游微服务实例确实没有正确地拿到 xid。

为什么会这样呢?

此时,答案已经呼之欲出了。全局事务ID——xid 正常地产生和传递到下游微服务实例了,然而看似是成功被下游微服务实例接收了,但是只是接收,并没有接到。上游微服务实例传了,下游微服务实例接了,但是没接到。“没接到”的意思就是到达下游微服务实例的请求中是有xid参数的,但是xid参数并没有被正常处理。xid的传递在终点出现了问题,导致了全局事务和分支事务“失联”了。

那么,下游服务实例中xid参数接收和处理的类在哪里呢?在com.alibaba.cloud.seata.web.SeataHandlerInterceptor 类中,源码及注释如下:

public class SeataHandlerInterceptor implements HandlerInterceptor {

	private static final Logger log = LoggerFactory
			.getLogger(SeataHandlerInterceptor.class);

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
			Object handler) {
        // 获取绑定后的xid
		String xid = RootContext.getXID();
        // 获取请求头中的xid
		String rpcXid = request.getHeader(RootContext.KEY_XID);
		if (log.isDebugEnabled()) {
			log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
		}
		// 如果未绑定
		if (StringUtils.isBlank(xid) && rpcXid != null) {
            // 绑定xid
			RootContext.bind(rpcXid);
			if (log.isDebugEnabled()) {
				log.debug("bind {} to RootContext", rpcXid);
			}
		}

		return true;
	}
	... 省略部分代码

这个拦截器可以说是 xid 传递过程的终点,下游微服务实例会在这里接收请求头中的 xid 参数并进行绑定操作。如果这个拦截器中的方法正常运行的话,那么 xid 的传递就不会出问题,全局事务和分支事务也不会“失联”了。

查找问题过程中,我在这个拦截器的 preHandle() 方法中打了断点,然后在验证过程中根本没有进入过这些断点上,也就是说这个拦截器根本没起作用。为什么这个拦截器没起作用呢?因为没有根本配置这个拦截器。

passenger-service 和 order-service 两个项目中分别定义了PassengerServiceWebMvcConfigurerOrderServiceWebMvcConfigurer 两个类并继承了 WebMvcConfigurationSupport,如果一个拦截器要生效的话需要在这里进行配置。

解决办法就是在这两个项目中配置 SeataHandlerInterceptor 这个拦截器生效即可,代码如下:

public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**");
}

好的,到这里,“分支事务不回滚”的问题就解决完了,一切都正常了。饶了那么一大圈、花费了那么多时间、分析了一堆源码,结果仅仅是因为这个拦截器没配置。

复盘总结

总体来说,这个问题还是围绕 Seata 分布式事务处理中“全局事务的开启与处理”和“xid的产生与传递”这两个知识。

从前一天下午发现这个问题,然后没处理掉。晚上十一点继续处理这个问题,折腾到第二天凌晨四点左右才处理和验证完成。

其实一开始就进入了误区,我以为代码是正确的、配置是正常的。不过,好在没有一直轴,发现情况不对赶紧换个思路,分析整个过程和源码,最终找到了问题的根由。

其实在真实的业务开发中,也有可能遇到这种情况。比如,写个简单的demo 或者小功能一切都正常,但是真的拿到企业开发的项目里,直接拉闸。毕竟写 demo 不会考虑太多,涉及的代码也少,能跑就行,而真实项目中有些被忽略掉的或者说自己不熟悉的配置,这也是需要注意的点。

另外,扩展一下这个知识点。收集广大网友的踩坑记录,除了“未配置SeataHandlerInterceptor”会导致“分支事务不回滚”的问题之外,全局事务失败的原因一般还有如下几种情形:文章来源地址https://www.toymoban.com/news/detail-523414.html

  • 代码中的配置错误或者配置项有遗漏,导致报错。处理办法:检查配置,因为粗心或者漏掉了一些,修改正确即可。
  • 数据源未被Seata代理,即未正确配置io.seata.rm.datasource.DataSourceProxy 类。处理办法:修改代码,手动或者自动配置DataSourceProxy。
  • 依赖版本升级导致的全局事务失效,笔者之前遇到过的,从seata-spring-boot-starter 1.3.0 升级到 1.4.2 时,由于 Seata 数据源自动配置逻辑的调整导致的。处理方法就是手动配置一下数据源代理。

到了这里,关于微服务中 Seata “分支事务不回滚”问题的复盘的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微服务--Seata(分布式事务)

    TCC模式在代码中实现:侵入性强,并且的自己实现事务控制逻辑 Try,Confirm() cancel() 第三方开源框架:BeyeTCCTCC-transactionHimly 异步实现:MQ可靠消息最终一致性 @GlobalTransacational---AT模式

    2024年02月10日
    浏览(39)
  • 微服务系列文章之 seata 事务模式

    XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。 XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。 两阶段提交 XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。

    2024年02月12日
    浏览(30)
  • 08-微服务Seata分布式事务使用

    一、分布式事务简介 事务ACID: A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失 败的情况。 C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转100元, 转账前和转账后的

    2024年01月24日
    浏览(38)
  • 微服务13-Seata的四种分布式事务模式

    XA模式分为两种情况 : 提交成功: 提交失败: 具有强一致性seata相当于是在RM上做了一层封装; XA模式 优点 : 1.事务的强一致性,只要有失败的,TC事务协调者就会发送信息让RM回滚——满足ACID原则 2.没有代码侵入,常用数据库都支持 缺点 : 1.第一阶段就要锁定数据库资源

    2024年02月07日
    浏览(34)
  • 【springcloud微微服务】分布式事务框架Seata使用详解

    目录 一、前言 二、事务简介 2.1 原子性 2.2 一致性 2.3 隔离性 2.4 持久性

    2023年04月26日
    浏览(32)
  • 【SpringCloud】微服务保护(Sentinel)和分布式事务(Seata)

    建议学完该系列前篇 【SpringCloud】商城项目拆分微服务各个组件学习 然后再来看本文 然后学习 【RabbitMQ】交换机、队列、可靠性保证和延迟消息 在微服务远程调用的过程中,还存在几个问题需要解决。 首先是业务健壮性问题: 例如在之前的查询购物车列表业务中,购物车

    2024年01月25日
    浏览(47)
  • Java微服务分布式事务框架seata的TCC模式

    🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 专栏 描述 Java项目实战 介绍Java组件安装、使用;手写框架等 Aws服务器实战 Aws Linux服务器上操作nginx、git、JDK、Vue Jav

    2024年03月23日
    浏览(35)
  • 分布式事务篇-2.1 阿里云轻量服务器--Docker--部署Seata

    本文介绍基于Seata 解决分布式事务,Seata 是一个服务所有首先需要对其进行部署。 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 拷贝完毕删

    2024年02月11日
    浏览(33)
  • 若依微服务 + seata1.5.2版本分布式事务(安装配置nacos+部署)

    若依官方使用的1.4.0版本seata,版本较低配置相对更麻烦一些 一、seata服务端下载,下载方式介绍两种入口,如下: 1、找到对应版本,下载 binary 即可。 下载包名为:seata-server-1.5.2.zip 2. github上下载   Releases · seata/seata · GitHub  找到对应的1.5.2版本,每个版本下都有一个缩放

    2024年02月09日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包