2019独角兽企业重金招聘Python工程师标准>>>

解Bug之路-Druid的Bug

笔者很热衷于解决Bug,同时比较擅长(网络/协议)部分,所以经常被唤去解决一些网络IO方面的Bug。现在就挑一个案例出来,写出分析思路,以飨读者,希望读者在以后的工作中能够少踩点坑。

前言

此Bug是Druid低版本的Bug,此Bug至少在1.0.12版本就已经修复。

Druid的Bug现场

在紧张的新项目开发的日子里,突然收到线上某系统的大量报警,对应系统的人员发现此系统在某一台机器上dump了大量的error日志。日志基本都是:

Druid:  GetConnectionTimeoutException

此系统所有用到数据库的地方都抛出此异常。于是祭出重启大法,重启过后,一切Okay。然后对应的系统人员开始排查这个问题,一直没有结果。
过了两天,又收到此类型的error日志报警,而且这一次是有两台系统同时爆出此种错误。紧急重启后,将此问题紧急报到我们这边处理。鉴于本人有丰富的IO处理经验,当然落到了本人头上。

Bug复盘

此系统是通过Druid连接后面的数据库分库分表Proxy,再由此Proxy连接后面的数据库。示意图如下所示:

缩小Bug范围

获取连接超时(GetConnectionTimeoutException)此错误的出现,只有两种可能:

1.业务系统本身Druid获取连接失败。
2.作为中间件的Sharding Proxy获取连接失败。

在这个Bug里面很明显是Druid创建连接失败,原因如下:

1.此系统有10多台机器,仅仅有两台出现此种故障。
2.这两台重启后一切正常。

如果说这两台是由于同机房问题出现统一的网络连接异常,那么并不能解释重启后正常这一现象。

Druid问题定位

于是开始分析为何获取连接超时,第一步当然是开始寻找源码中日志抛出异常点。上源码:

DruidConnectionHolder holder;
.......
try {if (maxWait > 0) {holder = pollLast(nanos);}else {holder = takeLast();}......
}finally {lock.unlock();
}
if(holder == null){......if (this.createError != null) {throw new GetConnectionTimeoutException(errorMessage, createError);} else {throw new GetConnectionTimeoutException(errorMessage);}
}

可见,这边获取到的DruidConnectionHolder为null,则抛出异常。

Druid获取连接的过程

在分析这个问题之前,先得看下Druid是如何创建连接的,下面是本人阅读Druid源码后画的示意图:

可见druid创建连接都是通过一个专门的线程来进行的,此图省略了大量的源码细节。

为何Holde为null?

继续上源码

private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException {for (;;) {if (poolingCount == 0) {emptySignal(); // send signal to CreateThread create connectionif (estimate <= 0) {waitNanosLocal.set(nanos - estimate);return null;}...try {long startEstimate = estimate;estimate = notEmpty.awaitNanos(estimate); ......} finally {......}......if (poolingCount == 0) {if (estimate > 0) {continue;}waitNanosLocal.set(nanos - estimate);return null;}}decrementPoolingCount();DruidConnectionHolder last = connections[poolingCount];connections[poolingCount] = null;return last;}
}

可见,如果触发条件,estimate<=0,则返回null。
上述源码的过程示意图如下:

继续追踪

由此可见,在获取连接的时候一直超时,不停的爆GetConnectionTimeoutException异常,明显是由于创建连接线程出了问题。那到底除了什么问题呢?由于Druid的代码比较多,很难定位问题点,于是还从日志入手。

进一步挖掘日志

错误信息量最大的是最初出现错误的时间点,这是笔者多年排查错误的经验总结。由于正好有两台出错,比较其错误的共同点也能对解决问题大有裨益。

大量create connection error

当笔者和同事追查错误的源头的时候,在有大量的create connection error,奇怪的是,到了一个时间点之后,就只剩GetConnectionTimeoutException异常了。
继续分析,在出现create connection error的时候,还是有部分业务请求能够执行的。即获取到了连接。
翻了翻源码,create connection error是在创建连接线程打印的,如果这个异常没有继续打印,而同时连接也获取不到,就在很大程度上表明:

创建连接根本就是被销毁了!

错误分界点

于是开始寻找什么时候create connection error开始销毁。于是通过笔者和同事在无数的错误日志中用肉眼发现了一个不寻常的日志隐蔽在大量的错误日志间:

Druid:create holder error

在这个错误出现之后,就再也没有了create connection error,翻了翻另一台机器的日志,页是同样的现象!

源码寻找Bug

隐隐就感觉这个日志是问题错误的关键,打出这个日志的源码为:

创建连接线程
@Override
public void run() {runInternal();
}private void runInternal() {for (; ; ) {......try{holder = new DruidConnectionHolder(DruidDataSource.this,connection);}catch(SQLException ex){LOG.error("create connection holder error",ex);// 这句break就是罪魁祸首// 至少在1.0.12修复break;}......}
}

这边竟然在for(;;)这个死循环中break了!!!,那么这个线程也在break后跳出了死循环从而结束了,也就是说创建连接线程被销毁了!!!如下图所示:

为何create holder error?

继续搜寻日志,发现create holder error之前,会有获取事务隔离级别的报错。那么结合源码,错误的发生如下图:

即在Druid的创建连接线程创建连接成功之后,还需要拿去数据库的holdability,isolation(隔离级别)等MetaData,在获取MetaData失败的时候,则会抛出异常,导致创建连接线程Break。
但是如果在handshake的时候就失败,那么由于Druid处理了这种异常,打印create connection error,并继续创建连接。
于是就有了在create holder error之前大量create connection error,在这之后没有了的现象。

完结了么?

Druid的Bug是弄清楚了,但是为何连接如此不稳定,有大量的创建连接异常,甚至于Druid前脚创建连接成功,后脚发送命令就失败呢?

Sharding Proxy的Bug

于是此问题又萦绕在笔者心头,在又一番不下于上述过程的努力之后,发现一个月之前上线的新版本的Sharding Proxy的内存泄露Bug导致频繁GC(并定位内存泄露点),导致了上述现象,如下图所示:

由于每次内存泄露过小,同时Sharding Proxy设置的内存过大。所以上线后过了一个月才有频繁的GC现象。之前上线后,大家观察了一周,发现没有任何异常,就不再关注。与此类似,如果DB负载过高的话,笔者推测也会触发Druid的Bug。

最后处理

笔者去翻Druid最新源码,发现此问题已经修复,紧急联系各业务线升级Druid,同时让Sharding Proxy负责人修改了最新代码并上线。
终于这次的连环Bug算是填完了。

总结

追查Bug,日志和源码是最重要的两个部分。最源头的日志信息量最大,同时要对任何不同寻常的现象都加以分析并推测,最后结合源码,才能最终找出Bug。

原文链接

https://my.oschina.net/alchemystar/blog/899987

转载于:https://my.oschina.net/alchemystar/blog/899987

解Bug之路-Druid的Bug相关推荐

  1. 解Bug之路-串包Bug

    解Bug之路-串包Bug 笔者很热衷于解决Bug,同时比较擅长(网络/协议)部分,所以经常被唤去解决一些网络IO方面的Bug.现在就挑一个案例出来,写出分析思路,以飨读者,希望读者在以后的工作中能够少 ...

  2. 解Bug之路-主从切换”未成功”?

    解Bug之路-主从切换"未成功"? 前言 数据库主从切换是个非常有意思的话题.能够稳定的处理主从切换是保证业务连续性的必要条件.今天笔者就来讲讲主从切换过程中一个小小的问题. 故障 ...

  3. 多次执行sql 后卡住_解Bug之路记一次中间件导致的慢SQL排查过程

    解Bug之路-记一次中间件导致的慢SQL排查过程 前言 最近发现线上出现一个奇葩的问题,这问题让笔者定位了好长时间,期间排查问题的过程还是挺有意思的,就以此为素材写出了本篇文章. Bug现场 我们的分 ...

  4. sql 在某段时间_解Bug之路记一次中间件导致的慢SQL排查过程

    解Bug之路-记一次中间件导致的慢SQL排查过程 前言 最近发现线上出现一个奇葩的问题,这问题让笔者定位了好长时间,期间排查问题的过程还是挺有意思的,就以此为素材写出了本篇文章. Bug现场 我们的分 ...

  5. 解Bug之路-Nginx 502 Bad Gateway

    解Bug之路-Nginx 502 Bad Gateway 前言 事实证明,读过Linux内核源码确实有很大的好处,尤其在处理问题的时刻.当你看到报错的那一瞬间,就能把现象/原因/以及解决方案一股脑的在 ...

  6. 世界树服务器bug位置,四叶草剧场世界树bug怎么获得 世界树bug神器获取位置图文详解...

    核心提示:四叶草剧场世界树bug怎么获得?在四叶草剧场的游戏中,世界树的爬塔挑战是最近玩家们正在参与的,不少玩家不知道BUG神器在哪一层怎么获得,接下来小编就为大家详细的介绍一下四叶草剧场世界树bug ...

  7. 软件测试bug文档模板,软件bug测试记录模板

    软件bug测试记录模板 XXX软件bug测试记录表 文档编号: 背景信息 项目名称 测试目的 硬件环境 软件环境 测试时间 测试人员 测试说明 1.严重等级: A-Crash(崩溃的):由于程序所引起 ...

  8. 测试:bug的生命周期、bug的等级、如何描述一个bug

    一.Bug 的生命周期 new - open - fixing - verify - close 发现bug–>提交bug–>指派bug–>研发确认bug–>研发去修复bug– ...

  9. 软件测试中Bug的生命周期以及Bug的严重等级

    Bug的生命周期中有很多个状态,下面我就为大家比较细致的罗列出一个Bug从它被创建到关闭的过程: 1.首先当测试人员接到一个项目或产品准备测试的时候,测试人员会根据测试用例一步步的来执行用例进行简单的 ...

最新文章

  1. ViewPager 入门一
  2. 从技术上还原入侵雅虎服务器是怎么一回事
  3. php传输html乱码解决
  4. cassert与NDEBUG,_DEGUG
  5. C语言程序设计之回调函数实现方法
  6. C++ 集成和派生练习题解答
  7. 奇异值分解 本质矩阵_Singular Value Decomposition(奇异值分解)
  8. java之servlet学习基础(一)
  9. SQL SERVER 和ACCESS/excel的数据导入导出
  10. 零基础多久能学会python_零基础小白多久能学会python
  11. matlab示例程序,matlab示例程序
  12. App下载的视频导进电脑中生成.mp4文件的方法
  13. 流利阅读 2019 1.6 What happened at Theranos is a dazzling story of deception
  14. ftp文件服务器怎么迁移,ftp文件服务器迁移
  15. FAT文件系统引导扇区学习总结
  16. day16-正则表达式
  17. 旅游网站毕业设计,旅游网站网页设计设计源码,旅游网站设计毕业论文
  18. 《卓有成效的管理者》第一次心得
  19. 防止360浏览器小窗下载视频
  20. golang最适合(擅长)做什么

热门文章

  1. xamarin 学习笔记02- IOS Simulator for windows 安装
  2. Linux (CentOS)增加删除用户
  3. 构造函数中不应调用虚函数
  4. LINQ学习(六):OrderBy/Group By子句
  5. 《当程序员的那些狗日日子》(十五)首次接单
  6. 移动的验证码安全问题告诉移动网站后......,1860意指一般人不会这样做.
  7. 【Groovy】闭包 Closure ( 闭包定义 | 闭包类型 | 查看编译后的字节码文件中的闭包类型变量 )
  8. 【错误记录】Flutter 混合开发获取 BinaryMessenger 报错 ( FlutterActivityAndFragmentDelegate.getFlutterEngine() )
  9. 【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 )
  10. 【Netty】NIO 缓冲区 ( Buffer ) ( 缓冲区读写类型 | 只读缓冲区 | 映射字节缓冲区 )