记一次找不到JDBC驱动Jar包的问题定位

问题现象:

DriverManager类中抛出一条”No suitable driver for url: xx”错误信息。后续进行的jdbc操作就失败了。

背景

我们的产品是一个平台,有一个能力是将业务同事的Jar包放到我们启动的SparkContext中执行(不熟悉Spark的朋友可以理解成我们提供了一个运行环境来执行业务代码,用户只需要写业务逻辑代码,如何调度,高效的运行是我们来完成的)业务同事会通过我们提供的一套SDK,去连接文件存储系统(HDFS),执行Spark的一系列操作,和其他的业务逻辑,并通过SDK传递参数。

初步分析:

抛出这条错误信息,直译就是找不到合适的驱动。
想当然的可以认为是

  1. url拼错了,所以找不到驱动。
  2. 驱动jar包没有,所以找不到驱动。

和业务同事确认后

  1. 是偶现问题,代码没有变化,所以url没有拼错
  2. 我们是通过HDFS来存放Jar包的,HDFS路径上Jar包存在,大小没有问题,不会是文件损坏的问题。 权限属主也没有问题。

进一步分析:

目前的线索还是那一条错误信息,排除了一些低级错误,接下来还是要围绕唯一的可靠线索继续确认。

  1. 我们的运行环境是否加载了那个Jar包(Spark Driver是我们自己实现的,启动时会加载业务Jar包,并打印日志,包括时间,文件名,大小。因为我们同一套环境可能会运行多个业务,所以会将每个业务使用的Jar放进各自的Classloader来进行处理。一些通用的Jar比如平台公共能力,Jdk,Spark的一些Jar会放到默认的Classloader去执行)
  2. 打印那条错误信息的是DriverManager,查看源码可以分析为什么会报这个错误。

因此可以通过两个方向(从上往下,从下往上)分别定位,直到定位出根因。

  1. 通过Driver日志确认了我们正确的加载了该Jar包至用户的Classloader
  2. DriverManager只是在需要连接数据库时查询了一遍registerDriver这个集合,查找目前已经注册的驱动是否有符合url的驱动。通过现象显而易见是没找到,那么没找到又可以进一步分析原因。
    1) 驱动Jar有问题,驱动Jar会实现一个accessUrl方法,让DriverManager来判断这个url是不是用我这个驱动。一般不会出问题,就算出现了偶现问题也是个致命问题,该数据库虽然是我司自研的,但内部也大量使用了,应该不会犯这种错误。不过我还是通过反编译查看了驱动Jar的该方法,该方法实现的很简单,不会出现问题。
    2)查看registerDriver是什么时候更新Jar的,通过源码发现只有在DriverManager初始化时才会往registerDriver塞驱动。
  3. 接下来就是观察registerDriver是不是真的没有(马后炮来看这步可以省略,通过现象来看就是没有,应该直接分析为什么没有塞进去)通过arthas,getStatic查看registerDriver变量,确实能看到如果成功的场景是有该驱动的,失败的场景是没有该驱动的。进一步通过classloader --url-stat查看驱动Jar包的类,发现成功场景是在Used Urls里面的,而失败场景是在UnUsed Urls里面的,也就是Classloader url没有问题,但是没有使用,我怀疑是使用方法有问题。这我就去和业务同事和该数据库支撑同事沟通,业务同事说使用方法没问题。数据库支撑同事也说没有问题,我看一些jdbc连接数据库demo中写了用Class.forname来加载驱动类,但有的也没有写。通过和大模型聊天后发现在JDBC4.0有一个特性,JDBC4.0通过SPI的方式提供了自动加载驱动Jar的能力,只需要驱动开发人员在一个指定路径填写一个文件,文件内容是驱动主类就可以自动加载了。那么显然,就是自动加载的能力失效了。

规避手段

为了尽快的解决问题,我和业务同事沟通后,让他们修改代码,在连接前使用Class.forname显示的加载类。果然,这样修改后就解决了问题。

根因分析

至于为什么自动加载机制失效了,我认为值得分析根因,避免只解决了表面现象,后续还会出现。
其实上面内容已经说了失效的各种条件,只是没有联系到一起。这里直接揭晓谜底了,结合到registerDriver的生成机制,DriverManager所属的classloader属于是上面所说的公共Classloader,而驱动所属的Classloader是业务自己的独立Classloader,当DriverManager初始化时,不能保证业务独立的Classloader已经生成成功,另外该Classloader我们可能会动态的修改classpath,所以DriverManager初始化时不一定能加载到业务Jar。和上面的各种现象都吻合了。所以我们这种为了业务隔离而建立Classloader的机制,和JDBC4.0的自动加载驱动能力是冲突的,或者是不能友好兼容的。而为了隔离机制,我们也不能将DriverManager所属Jar,和业务使用的驱动Jar放入同一个Classloader。所以只能让我们的业务同事整改代码,如果想使用我们产品,对于数据库连接的场景,必须要用Class.forname显示的加载驱动Jar,才能进行连接等后续操作。

借题发挥

事后回顾整个过程,我认为这是一道很好的面试题,可以考察候选人分析问题的能力,熟悉哪些工具,基本功掌握的是否扎实。这种能力很体现工作能力和经验。但只刷八股文,刷算法题是不具备的。面试题可以按以下顺序抛出,在候选人没有思路时可以适当引导。

  1. JDBC连接过程是什么样的
    至少可以说出要拼接出url,然后通过getConnection获取连接。
  2. 我们知道JDBC可以连接很多驱动,这些驱动都有各自的Jar包。那这些Jar包是如何加载的?
    1)如果可以说出Class.forname,说明知道如何加载一个类。说不出来的话可以引导抛开JDBC,Java的classloader如何加载Jar包。
    2)JDBC4.0,提供了自动加载能力。如果可以进一步说明如何实现的,那证明看过相关代码,或者看过类似的课程,广度和深度都可以。
  3. 切入正题,JDBC连接时报找不到合适驱动,如何分析(建议给候选人看日志截图,然候选人自己发掘信息,比如看到类是DriverManager,可以允许候选人查看jdk源码进一步分析)
    1)URL拼接错误
    2)JAR没有放到指定位置/权限/属主不对/文件损坏
    3)驱动包本身是否有问题,比如acceptsUrl写的有问题,register方法写的有问题(考察是否看过相关jdk源码,现看能否分析)
    4)classpath是否加载到,如何确认(考察会不会使用arthas或其他工具)
  4. 写class.forname没问题,不写有问题为什么
    因为自动加载加载机制失败,具体失败原因通过问题背景继续引导(业务使用自己独立classloader为了隔离和其他业务不冲突,可能会动态的修改classpath)
  5. 找到根因后如何解决,为什么解决。
    因为已经交代了业务背景,为了不影响原有功能,应当让使用方修改。可以考察候选人处理问题能力,如何修改才是更优的选择。
  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值