Spark连接池死锁问题解决及处理过程记录
2018-02-15 09:04
627 查看
1. 背景
由于业务需求驱动,财务系统需要定时从库存事物数据库,及订单接口,价格接口等数据库实例或接口系统获取数据并按设定的动态逻辑规则生成账单及明细.面对复杂,海量数据来源以及灵活的多数据库分库实例,在众多的数据处理框架中,我们决定使用Spark框架来处理海量账单数据.库存事物相关数据库表以及账单相关表是按各自分库维度存放在各自上百个 MYSQL 数据分库实例上. 其中读取库存事物分库是通过Spark自管理的JDBC方式读取,而写入账单分库是我们手工通过JDBC写入(使用DRUID连接池).
2. 应用版本
spark : 2.1.0mysql : 5.7
jdk : 1.8
hadoop: 2.5.3
3. 问题
在业务正式上线后一段时间后,发现每两三个星期会出现一次Spark任务Hold住的情况,于是打开Spark UI跟踪分析这个问题,结果在Executor的Thread dump中发现了BLOCKED的线程,且久久不能释放.图1
4. 问题分析
4.1 Spark线程锁的分析
从 图1 上可以看出, Thread ID : 125 , Thread ID:139 的线程在DriverRegistry$.register方法的45行BLOCKED住,而 Thread ID:127 线程一直HOLD 在RUNNABLE状态.而从栈信息上看很可能是在 DriverRegistry$.register方法46行处等待资源中.
于是打开Spark DriverRegistry类源代码,其register代码如下所示
结合Spark源代码分析,初步可以推断出:
线程 Thread ID:127 在静态方法 register(className:String) 的 val wrapper = new DriverWrapper(cls.newInstance().asInstanceOf[Driver]) 可能处于等待状态,
而线程 Thread ID:125 , Thread ID:139 要执行 register(className:String) 方法的45行时,因为 Thread ID:127 持有的 synchronized代码块 的锁没有释放,所以一直处于BLOCKED状态.
4.2 Spark 与 Druid 互锁分析
接下来的疑问是: 为什么 Thread ID:127 会处于等待状态呢?顺着 图1 继续往下看,发现 Thread ID:128 在 com.alibaba.druid.proxy.DruidDriver.registerDriver(DruidDriver.java:92) 处也是处于 RUNNABLE 状态.
其源代码如下:
public class DruidDriver implements Driver, DruidDriverMBean { static { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object More ...run() { registerDriver(instance); return null; } }); } public static boolean registerDriver(Driver driver) { ... DriverManager.registerDriver(driver); //92行 ... } }
4.3 DriverManager代码死锁分析
从前面已知的信息来看,解决我们疑问的只能在com.mysql.jdbc.Driver, DriverManager 类中找,接下来我们看一下这两个类的源代码因此我们目前的线程栈信息上看,Spark 的 Thread ID:127 获取加载了mysql驱动并初始化DriverManager类,而DriverManager类初始化时会执行loadInitialDrivers(),并且这个方法会加载当前classPath下/META-INF/services/java.sql.Driver文件中的类(这个文件中包含有com.mysql.jdbc.Driver及其他数据库驱动).
这时候 Thread ID 128 通过Druid连接池获取连接时,刚持有DriverManager类型的锁,
要等待Spark Thread ID:127 加载完DriverManager类,而 Thread ID:127 类又需要使用DriverManager类来加载driver class时,要等待 Thread ID 128 释放DriverManager类型锁,这样两个线程就互锁了。
5. 问题解决方法
解决的方法是让驱动类的加载过程变为单线程加载方式.在我们代码中调用Druid连接池获取Connection之前,串行执行DriverRegistry.register("com.mysql.jdbc.Driver");
6. 参考资料
具体JDBC驱动类初始化死锁问题可以参考 你假笨 的 JDK的sql设计不合理导致的驱动类初始化死锁问题
相关文章推荐
- 记录一次bug解决过程:eclipse Installed JREs 配置引出的问题
- 记录安装opencv的过程和碰到的问题以及解决方法
- Ubuntu中python环境下import requests错误的解决(学习过程问题记录)
- 文章标题 spark读取文件过程中发现的问题解决记录
- 安装MSYS2过程遇到的问题及解决记录
- cocos2d-x 3.4版本游戏打包AKP (重点记录如何解决打包过程中遇到的各种问题)
- 【解决办法】记录自动化测试持续集成过程中遇到的问题与解决方法
- struts2改springmvc过程中问题及解决办法记录
- LoadRunner 使用过程中问题及解决办法记录
- verilog学习过程中待解决的问题,先记录下来
- SweetAlert2 使用过程中弹框报错问题记录与解决
- 看《OpenGL超级宝典(第四版)》的4.5.2节时遇到了一系列问题,经过不懈努力终于解决,现将过程记录在下,以便查找追思。
- wamp部署https过程记录及无法启动问题解决
- 【Linux】无法添加用户,报“useradd: cannot open /etc/passwd”问题解决过程记录
- 一个罕见的MySQL redo死锁问题排查及解决过程(pt-pmp)
- 使用PowerBuilder 9编绎DLL类型,有点问题.处理过程记录如下.”Error opening file ‘c:\windows\system32\cgen\en32t.h’”
- 最近在项目中实践了一下Redis,过程中遇到并解决了若干问题,记录之.
- 【开卷有益】记录一次高并发下的死锁解决思考过程
- 记录linux学习过程中,遇到的问题与解决办法
- CentOs下安装Php的过程记录以及发现的问题和解决办法