问题描述

  • 线上突然出现Dubbo超时调用,时间刚好为Consumer端设置的超时时间。
  • 有好几个不同的接口都报超时了
  • 第1次调用超时,第2次(或第3次)重试调用非常快(正常水平)
  • Dubbo调用超时的情况集中出现了3次,每次都是过一会自动恢复

排查

排查日志

看到调用超时,首先就拿着traceId去服务提供方查日志。

奇怪的是,在服务提供方的业务日志里面,只有正常的调用日志(耗时正常),没有超时调用的日志。

从正常的调用日志里面看,一切都是正常的,看不出所以然。

给人的感觉就是超时那次请求的调用没有达到服务提供方。

此时系统活动情况

通过系统历史监控,我们发现除了gc比平时稍微高一点外(也在正常水位),没有其他的异常;CPU、内存、网络等指标都在正常范围。

查看Dubbo线程活动情况

第2次系统集中超时报警的做的第一件事就是登录到那台服务器查看dubbo线程活动情况:看下能不能找到阻塞在哪一行代码。很遗憾,所有的dubbo线程都没有阻塞,都是正常的WAITING状态。

并没有明显表明阻塞在某段代码,这可难倒我们了:如果没有阻塞的话,为什么dubbo调用方会报超时?继续看代码

该接口是否存在阻塞的代码?

硬着头皮重新看代码每一个分支,突然发现底层的一个方法中有http调用!会不会是这个http调用导致的超时?如果是的话,那么不同的接口调用超时的情况就说的通了,因为上层大部分接口都会调用这个底层方法。

怀揣着激动的心,仔细看了http调用的逻辑:用的是JDK提供的HttpURLConnection,其中只用了HttpURLConnection#getContentLength方法,并且也在finally代码块中将这个连接关闭了。好像也不是这个引起的,起初还以为getContentLength会把文件给下载下来,但是看了接口文档以后发现只会去读头信息中的ContentLength。

    /**
* Returns the value of the {@code content-length} header field.
* <P>
* <B>Note</B>: {@link #getContentLengthLong() getContentLengthLong()}
* should be preferred over this method, since it returns a {@code long}
* instead and is therefore more portable.</P>
*
* @return the content length of the resource that this connection's URL
* references, {@code -1} if the content length is not known,
* or if the content length is greater than Integer.MAX_VALUE.
*/
public int getContentLength() {
long l = getContentLengthLong();
if (l > Integer.MAX_VALUE)
return -1;
return (int) l;
}

代码阻塞的情况可能性也不大,因为重试请求不会超时:如果代码阻塞,那么重试请求大概率也会超时。

数据访问层是否有异常情况

既然代码没有阻塞,那么有没有可能是数据访问层的异常造成的呢?毕竟不止一个接口存在超时的问题,如果是底层数据访问层的异常导致,那么也说得通。

重点排查了mysql,但结果是令人失望的:并没有慢SQL;并且dubbo超时期间,mysql实例的CPU和内存水位都是正常的。

除了mysql、redis实例本身指标正常外,基于上面同样的理由:如果数据访问层有问题,那么重试基本上也会超时。所以数据访问层导致超时的线索也被排除。

有没有可能是Dubbo层面的问题

排查再次陷入僵局,逼迫着我们重新梳理排查思路:

  1. 除了代码阻塞
  2. 除了数据访问层异常
  3. 除了超时请求,其他请求的日志都是正常的

那么还有可能会导致超时呢?会不会是Dubbo本身异常导致的?

此时有一个关键的线索进入我们的视野:超时的那次请求去哪儿了?

在服务提供方的日志里面没有超时请求的的日志,只有重试请求成功的业务日志。太奇怪了,就算超时总的留下日志的吧,日志都不留,欺负我胖虎吗?!

到这里想到超时的请求可能是一个突破口,于是开始看Dubbo的相关的源码和文档。

从官方文档中的服务端调用链一层层往下查

AllChannelHandler源码中看到了令人兴奋的注释:



兴奋之余,为了避免理解偏差,还特地用百度翻译了一下



没错,如果线程池已经满了,那么服务端不会返回,直到客户端超时!这不是正式我们碰到的问题吗?!

并且此时还没有进入业务代码,所以没有打印业务日志,这样就可以解释为什么没有服务提供方没有超时请求的日志了。

别激动,这里明明有返回threadpool is exhausted异常信息,怎么能说没有返回呢?

别急,这是另外一个项目引用的dubbo,版本是2.6.2。

回到出问题的那个项目,查看dubbo版本:2.8.6,查看AllChannelHandler源码:是的在2.8.6版本中,并没有返回这个错误



问题好像找到了,OK,剩下的就是验证了。

验证

准备

  • 将DubboServerHandler线程池的最大线程数调到5
  • 使用Apache Bench进行压测:200请求、并发10个线程

case1:复现问题

  • Dubbo使用2.8.8版本
  • 预期:部分请求超时报错,重试耗时正常
  • 压测结果符合预期:部分接口报错超时,并且重试请求耗时正常

case2:验证猜想

  • Dubbo使用2.6.2版本
  • 预期:部分请求报错线程池耗尽threadpool is exhausted,并且重试大概率也会报该错误
  • 压测结果符合预期

至此,基本判定线上Dubbo调用超时的问题就是因为线程池耗尽引起的。

这个超时问题前前后后查了一周左右,排查过程中试了很多排查方向,为了叙述方便就没有展开。


避坑指南

  1. 合理设置Dubbo线程池大小。默认是200
  2. 合理设置超时时间。如果真出现了Dubbo调用超时的情况,合理的超时时间能够避免服务调用方被打爆
  3. Dubbo接口必须有返回值。从AllChannelHandler#received的源码和注释中可以看到只有有返回值的接口才会返回线程池耗尽的错误信息;其它的情况则不会将错误信息返回给调用方,直到调用方超时。

不可忽视的Dubbo线程池的更多相关文章

  1. 用了很多年Dubbo,连Dubbo线程池监控都不知道,觉得自己很厉害?

    前言 micrometer中自带了很多其他框架的指标信息,可以很方便的通过prometheus进行采集和监控,常用的有JVM的信息,Http请求的信息,Tomcat线程的信息等. 对于一些比较活跃的框 ...

  2. 动态线程池框架 DynamicTp v1.0.6版本发布。还在为Dubbo线程池耗尽烦恼吗?还在为Mq消费积压烦恼吗?

    DynamicTp 简介 DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为 动态调参.通知报警.运行监控.三方包线程池管理等几大类. 经过几个版本迭代,目前最新 ...

  3. dubbo 线程池

    在dubbo调用过程中被调用方有两个线程池:io线程池,业务线程池. 这也是dubbo调优的点. 配置信息: <dubbo:protocol name="dubbo" dis ...

  4. dubbo线程池的拒绝策略

    jdk自带的原生的拒绝策略抛出的异常信息不够详细,而dubbo对拒绝策略进行了改写,抛出的信息更具有参考价值,值得我们借鉴. jdk自带的原生拒绝策略抛出的信息: // ThreadPoolExecu ...

  5. dubbo线程池作用于接口而不是方法

    记一次线上dubbo服务超时和线程池满问题排查 可能调用的接口没问题,但是该服务中的其他接口占用完了线程池,导致调用超时被拒绝处理.

  6. Dubbo学习笔记8:Dubbo的线程模型与线程池策略

    Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 Ev ...

  7. 记一次线上dubbo服务超时和线程池满问题排查

    线上某dubbo服务A调用dubbo服务B的接口X方法,调用端A日志中出现了很多超时的情况,提供端B该接口X超时时间设置为60s: 查看提供端B的日志,报了很多线程池满的异常: Caused by: ...

  8. HIPPO-4J 1.3.0 正式发布:支持 Dubbo、RibbitMQ、RocketMQ 框架线程池

    文章首发在公众号(龙台的技术笔记),之后同步到个人网站:xiaomage.info Hippo-4J 距离上一个版本 1.2.1 已经过去一个月的时间.在此期间,由 8 位贡献者 提交了 170+ c ...

  9. 简单RPC框架-业务线程池

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

随机推荐

  1. Java类加载器概述

    Java类加载器概述 Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由Java 应用开发人员编写的. 系统提供的类加载器 引导类加载器 它用来加载 Java 的核心库,是用原生 ...

  2. Centos7 配置JDK 提示 /lib/ld-linux.so.2: bad ELF interpreter: No such file or direct

    解决办法:yum install glibc.i686

  3. Midway Serverless 发布 2.0,一体化让前端研发再次提效

    作者 | 张挺 来源 | Serverless 公众号 自去年 Midway Serverless 1.0 发布之后,许多业务开始尝试其中,并利用 Serverless 容器的弹性能力,减少了大量研发 ...

  4. Apache ShardingSphere 邀您相约 Open Source Day

    Apache ShardingSphere 很荣幸接受 AnitaB.org 的邀请参加 OpenSourceDay Summer 2021 活动. Open Source Day (OSD) 是一个 ...

  5. 接口自动化-Python3+request上传文件,发送multipart/form-data编码

    1.安装requests_toolbelt   pip install requests-toolbelt 2.发送文件中的数据 from requests_toolbelt import Multi ...

  6. 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析

    SudokuSolver 2.1 程序实现 在 2.0 版的基础上,2.1 版在输出信息上做了一些改进,并增加了 runtil <steps> 命令,方便做实例分析. CQuizDeale ...

  7. 7-Zip

    7-Zip https://www.7-zip.org/

  8. Serverless Kubernetes 和 Serverless on Kubernetes 的区别

    什么是 Kubernetes? Kubernetes 是一个可移植的.可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化. 什么是 Serverless ? 无服务器是一种云原 ...

  9. nsq - 一条消息的生命周期(一)

    经过前面几篇的学习,相信大家对nsq已经有了一个大概的了解,我在写这篇文章的时候也看了很多其他人写的教程,发现大家对于分析系统每个点写的很不错,但是都很少有整体串起来一起走一遍,所以,我打算分成2-3 ...

  10. [no code][scrum meeting] Beta 6

    $( "#cnblogs_post_body" ).catalog() 例会时间:5月19日11:30,主持者:黎正宇 下次例会时间:5月20日11:30,主持者:彭毛小民 一.工 ...