dubbo线程池打满问题解决-服务重启期间 mapstruct导致

背景

从前段时间开始,断续的有一个服务,在发布和服务重启的时候,偶发线程池打满问题,大概占比为 1/30 ~ 1/10 不等,可以通过重启解决恢复,具体问题一致没有定位出来

算是一个棘手的困难问题。分析了好多次,都只是怀疑。最近一次上线过程中,进行了一些代码的拆分打点,可以精准定位具体问题行数,再次进行复现

在最近一次发布中,问题又继续复现,终于定位并发现了问题所在。

具体表现形式如同下图监控所示,在服务刚启动之初,就会导致线程池满负载运行,不能释放

处理过程

抓取事故现场

  • 登录问题机器,抓取事故现场线程堆栈信息
arduino 复制代码
jstack -l pid > thread.dump 

简要分析问题

推荐一个在线的线程堆栈分析网站,能比较直观的针对线程对象做统计,稍后在过程中,会展现出来

链接地址 -fastthread

  • 进行堆栈数据,进行分析

可以观察到,此刻运行中的线程占比 81% ,非常不健康的一个值

进一步分析,发现其中 DubboServerHandler 与 default 运行线程基本打满,故此出问题问题已经定位。只需要精准分析 这 2 类线程组就可以发现问题

恢复现场

重启服务

问题分析

问题点定位

在 这个在线分析工具中,会针对相同的堆栈信息,做汇总,可以发现,有 200 多个线程,都是同样的,就很容易定位分析了

在下方可以看到具体的代码行数,进行进一步的分析和定位了

经过定位代码,发现不同类中,线程长时间运行不释放的一行代码都是同一行。并获取了前几次历史事故现场,进行同样的分析,发现问题基本类似。都是同样问题

都是通过 mapstruct 实现的 实例获取

ini 复制代码
DtoToBoConverter.INSTANCE.xxx;
SkuConverter.INSTANCE.xxx;

mapstruct自行了解

细节分析

由于 mapstruct 在我们项目中,使用很多,其余的项目从开始到现在,基本上没有出现过问题,并且发生问题的项目,并发流量并不算特别高。

针对这 2 个 convert 类进行仔细分析,发现一个特别的点,如下图所示

SkuConverter使用了 DtoToBoConverter

并且在 mpasturct 自动生成的实现类中,同样也生成了一行代码 Mappers.getMapper

出现问题行的 INSTANCE 的实现也是 Mappers.getMapper

在此刻就初步怀疑,是在并发情况下,由于 Mappers.getMapper() 内部的问题,导致的并发冲突导致。

有了猜测之后,就很容易进行问题的复现来推导猜测。从而定位问题

一般问题的发现和解决,我一般采取 猜测 --> 验证猜测 --> 确认 --> 解决 这四步走的方式来确认

问题复现

上边已经有了明确的猜测,然后就进行一些验证代码的涉及,并进行本地的实现。

由于问题场景为服务重启,要模拟这个场景会非常复杂,受限,并且在最初没定位问题的时候,在压测环境模拟过 n 次重启事件,由于场景缺乏,导致一致没有进行复现

  1. 创建 100 个线程
  2. 进行 1000 次循环,持续的往线程池中添加 Instance 实例化代码
  3. 检测线程池活跃线程状态,并实实时打印

设计的代码如下

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 1000; i++) {

            executor.submit(new Runnable() {
                                @Override
                                public void run() {
                                    DtoToBoConverter instance = DtoToBoConverter.INSTANCE;
                                }

                            });
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    SkuConverter instance = SkuConverter.INSTANCE;
                }

        });

            int activeCount = ((ThreadPoolExecutor) executor).getActiveCount();
            System.out.println("运行中"+ activeCount);
        }
        
        int activeCount = ((ThreadPoolExecutor) executor).getActiveCount();
        System.out.println("循环后"+ activeCount);

        while (((ThreadPoolExecutor) executor).getActiveCount() ==100){
            System.out.println("等待中"+ ((ThreadPoolExecutor) executor).getActiveCount());
            Thread.sleep(100);
        }

执行代码,输出结果为:

复制代码
运行中2
运行中4
·
·
·
运行中100
运行中100
运行中100
·
·
·
循环后100
等待中100
等待中100
等待中100

此时,线程状态持续处于活跃状态,不能释放,至此,事故现场本地模拟复现,线程打满了

通过 jprofiler 对当前正在执行的 java 进程进行监控,观察状态,获取线程堆栈

在这个线程堆栈中,可以看到具体的细节问题,长时间在运行在 newInstance 中

也可以明显的看到,所有的线程都处于运行状态

问题原因

问题既然已经复现了,并且也是在并发循环依赖加载的情况下,出现了问题。 深入源码看下

uses 的doc 中,明显提到了No cycle between generated mapper classes must be created

并且也在代码中出现了循环的依赖情况,导致了在进行类加载的过程中,出现了现成一直处于run的阶段 从而导致dubbo 线程池无法释放

解决思路

通过import 的方式来进行替换,解决 用uses情况下的一些依赖问题 达到相对应的结果

归纳总结

善于用一些工具,方便快速定位问题 本文工具 : jprofiler ,fastthread

  • 场景
  • 猜测
  • 实验验证
  • 解决规避
  • 收集入案例库
相关推荐
武子康1 天前
Java-75 深入浅出 RPC Dubbo Java SPI机制详解:从JDK到Dubbo的插件式扩展
java·分布式·后端·spring·微服务·rpc·dubbo
Easonmax2 天前
文心一言4.5深度评测:国产大模型的崛起之路
dubbo
武子康2 天前
Java-74 深入浅出 RPC Dubbo Admin可视化管理 安装使用 源码编译、Docker启动
java·分布式·后端·spring·docker·rpc·dubbo
武子康3 天前
Java-71 深入浅出 RPC Dubbo 上手 父工程配置编写 附详细POM与代码
java·分布式·程序人生·spring·微服务·rpc·dubbo
武子康3 天前
Java-72 深入浅出 RPC Dubbo 上手 生产者模块详解
java·spring boot·分布式·后端·rpc·dubbo·nio
悟能不能悟5 天前
Dubbo跨越分布式事务的最终一致性陷阱
分布式·wpf·dubbo
武子康5 天前
Java-70 深入浅出 RPC Dubbo 详细介绍 上手指南
java·分布式·网络协议·spring·rpc·dubbo·nio
向風而行6 天前
数据提取之lxml模块与xpath工具
dubbo
发仔12310 天前
Dubbo介绍及示例用法
java·dubbo
初九之潜龙勿用11 天前
文心一言4.5开源模型测评:ERNIE-4.5-0.3B超轻量模型部署指南
开源·dubbo·文心一言