实践Spark连接池死锁问题解决及处理过程记录

Posted 唯技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实践Spark连接池死锁问题解决及处理过程记录相关的知识,希望对你有一定的参考价值。

背景

由于业务需求驱动,唯品会财务系统需要定时从库存事物数据库及订单接口、价格接口等数据库实例或接口系统获取数据,并按设定的动态逻辑规则生成账单及明细。面对复杂、海量数据来源以及灵活的多数据库分库实例,在众多的数据处理框架中,我们决定使用Spark框架来处理海量账单数据。

库存事物相关数据库表以及账单相关表是按各自分库维度存放在各自上百个 MYSQL 数据分库实例上。其中读取库存事物分库是通过Spark自管理的JDBC方式读取,而写入账单分库则是我们手工通过JDBC写入(使用DRUID连接池)。

应用版本

【实践】Spark连接池死锁问题解决及处理过程记录

Spark: 2.1.0

MYSQL: 5.7

JDK: 1.8

Hadoop: 2.5.3

发现问题

【实践】Spark连接池死锁问题解决及处理过程记录在业务正式上线一段时间后,我们发现每两三个星期会出现一次Spark任务Hold住的情况,于时打开Spark UI跟踪分析这个问题,结果在Executor的Thread dump中发现了BLOCKED的线程,且久久不能释放。
【实践】Spark连接池死锁问题解决及处理过程记录

图1:spark_ui_lock.jpg


问题分析

【实践】Spark连接池死锁问题解决及处理过程记录

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连接池死锁问题解决及处理过程记录

结合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 状态。其源代码如下:

【实践】Spark连接池死锁问题解决及处理过程记录


3、DriverManager代码死锁分析

从前面已知的信息来看,解决我们疑问的只能在com.mysql.jdbc.DriverDriverManager 类中找,接下来我们看一下这两个类的源代码:

【实践】Spark连接池死锁问题解决及处理过程记录

图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类型锁,这样两个线程就互锁了。

问题解决方法

【实践】Spark连接池死锁问题解决及处理过程记录解决的方法是让驱动类的加载过程变为单线程加载方式.在我们代码中调用Druid连接池获取Connection之前,串行执行

【实践】Spark连接池死锁问题解决及处理过程记录

参考资料

具体JDBC驱动类初始化死锁问题可以参考你假笨的《JDK的sql设计不合理导致的驱动类初始化死锁问题》(点击阅读原文即可进入)。



推荐阅读


Real-Time Data Pipeline介绍之“实时性”篇


Real-Time Data Pipeline介绍之“高可用”篇

Spark在唯品会财务系统重构中的实践总结



欢迎投稿!!

只要是技术相关的文章尽管砸过来!


以上是关于实践Spark连接池死锁问题解决及处理过程记录的主要内容,如果未能解决你的问题,请参考以下文章

教程:Apache Spark SQL入门及实践指南!

在 Hibernate/C3P0 中处理连接池耗尽并避免死锁

PDO 连接池死锁现象分析

PDO 连接池死锁现象分析

PDO 连接池死锁现象分析

PDO 连接池死锁现象分析