实践Spark连接池死锁问题解决及处理过程记录
Posted 唯技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实践Spark连接池死锁问题解决及处理过程记录相关的知识,希望对你有一定的参考价值。
背景
库存事物相关数据库表以及账单相关表是按各自分库维度存放在各自上百个 MYSQL 数据分库实例上。其中读取库存事物分库是通过Spark自管理的JDBC方式读取,而写入账单分库则是我们手工通过JDBC写入(使用DRUID连接池)。
应用版本
Spark: 2.1.0
MYSQL: 5.7
JDK: 1.8
Hadoop: 2.5.3
发现问题
图1:spark_ui_lock.jpg
问题分析
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状态。
2、Spark与Druid互锁分析
接下来的疑问是: 为什么 Thread ID:127 会处于等待状态呢?
顺着图1继续往下看,发现 Thread ID:128 在 com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:103) 处也是处于 RUNNABLE 状态。其源代码如下:
3、DriverManager代码死锁分析
从前面已知的信息来看,解决我们疑问的只能在com.mysql.jdbc.Driver, DriverManager 类中找,接下来我们看一下这两个类的源代码:
图3:driver_code.jpg
因此我们目前的线程栈信息上看,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类型锁,这样两个线程就互锁了。
问题解决方法
参考资料
具体JDBC驱动类初始化死锁问题可以参考你假笨的《JDK的sql设计不合理导致的驱动类初始化死锁问题》(点击阅读原文即可进入)。
Real-Time Data Pipeline介绍之“高可用”篇
Spark在唯品会财务系统重构中的实践总结
欢迎投稿!!
只要是技术相关的文章尽管砸过来!
以上是关于实践Spark连接池死锁问题解决及处理过程记录的主要内容,如果未能解决你的问题,请参考以下文章