目录
Oracle的Lightweight Connection Validation
前言
本文介绍我是如何分析和解决一个数据库连接池慢的问题。
最近在做一个交易系统,这个系统处于整个交易链路上的一个节点,系统采用微服务架构, 上游系统对我们的时效要求较高,我们的API需要在400毫秒内返回结果到上游系统,否则该笔交易不收钱。而我们系统又依赖于另一个下游系统和Oracle数据库,结构图如下。
最近这个系统出现一些性能问题,其中一个问题是数据库连接慢,某些极端情况下大于400ms,甚至超过1s。我们采用HikariCP作为数据库连接池技术,以下是系统采用的关键技术。
JDK | 11 |
Oracle | 12c |
Oracle JDBC | ojdbc7 |
HikariCP | 4.0.3 |
分析问题
数据库连接池大小
说到数据库连接池的性能,我们通常首先会想到连接池的大小(pool size)。Hikari的官方文档描述了很多参数,其中两个参数(maximumPoolSize, minimumIdle)跟pool size有关的。于是我对其调参并进行性能测试,发现这两个参数调整并不能解决我们的问题,数据库连接慢的问题仍然存在。
虽然解决不了问题,但阅读文档的过程中让我对数据库连接池大小有了更深的理解。
"How big should your connection pool be? You might be surprised that the question is not how big but rather how small!"
请参考About Pool Sizing · brettwooldridge/HikariCP Wiki · GitHub
Hikari池化技术
简单调参不能解决我的问题,于是我开始阅读Hikari源代码,尝试理解它是如何管理连接池的。如下图,简单来说, 当有人向Hikari发起connection请求的时候,Hikari会从connection pool中随机选取一个已存在的connection,然后校验这个被选取connection是否存活(alive),如果alive则返回该连接,如果不alive,则关闭这个连接重新创建一个新的连接。详细请参考https://medium.com/@rajchandak1993/understanding-hikaricps-connection-pooling-behaviour-467c5a1a1506
Hack Hikari源码
在了解Hikari的工作原理后,我想通过打log的方法获取上述每个环节的耗时,对应的类和方法在这里,那可以改Hikari的源代码吗?答案是可以的!方法就是在我的工程内创建同一个包名com.zaxxer.hikari.pool(这个包的名称要与我想Hack的那个HikariPool类的包名一致)。然后在这个包下创建同一个类HikariPool.java,然后这个类的源代码从HIkari复制到我这个新建的HikariPool。当你的工程和依赖存在两个一摸一样的类的时候(类名和包名都一样),运行时是会优先选取工程的类来执行的。这时我在这个新建HikariPool.java里做一些改动,就是在每个关键环节结束时打印timestamp,那么我就可以分析耗时了。
通过这种方法我发现Hikari创建connection慢的原因在校验connection是否alive这个环节上。
那么Hikari是怎样校验connection是否alive的呢?代码在这里!
如果你使用的JDBC驱动支持JDBC4 Validation,那么就使用JDBC4 Validation。
否则,会执行预定义的Test query。
Oracle的Lightweight Connection Validation
什么是JDBC4 Validation?不同数据库驱动有不同Validation方法。我们项目使用的是Oracle 12c的Oracle JDBC 7驱动。Oracle官方文档有描述是怎样做connection validation的。以下引用自官方文档。
Starting from Oracle Database Release 18c, JDBC Thin driver supports lightweight connection validation. Lightweight connection validation enables JDBC applications to verify connection validity by sending a zero length NS data packet that does not require a round-trip to the database.
For the releases of Oracle Database earlier than 18c, when you call the isValid(timeout)
method to test the validity of a connection, Oracle JDBC driver uses a ping-pong protocol, which is an expensive operation as it makes a full round-trip to the database. Since Oracle Database Release 18c, the isValid(timeout)
method instead sends an empty packet to the database and does not wait to receive it back. So, connection validation is faster, which results in better application performance.
Lightweight connection validation is disabled by default. To enable this feature, you must set the oracle.jdbc.defaultConnectionValidation
connection property value to SOCKET
. If this property is set, then the JDBC driver performs lightweight connection validation, when you call the isValid(timeout)
method.
从这段文字可以看到,Oracle默认的connection validation是基于双向的ping-pong protocal,这是一种时间花销较高的校验机制。Oracle 18开始支持一种轻量的基于Socket的校验机制。
好啦,到此,我们终于找到数据库连接慢的原因了,就是Oracle的默认校验方法,是取决于网络的,如果网络不好,就会影响性能。
解决问题
找到了问题的根源,怎样解决呢?有两个方法:
- 提高带宽
- 改代码,使用新的Lightweight Connection Validation
鉴于在本公司,跟网络团队谈带宽一般无法谈,所以只有项目组自己改代码使用轻量的校验方法了。但是,首先要确定一个前置条件:我们使用的Oracle 12c支持新版本的轻量校验吗?在多次联系Oracle support团队确认后,Oracle 12c,只要使用最新版本的JDBC驱动(当前最新是JDBC 21.x),是可以支持这种轻量的校验方法的。
关于Oracle数据库版本及其JDBC驱动版本之间的兼容性,
关于如何使用轻量校验,官方文档也有介绍。这里不详述了。
最后,要补充说明这种校验机制的风险,以下引用自Oracle官方文档。
Lightweight connection validation checks only the underlying socket health. When the isValid(timeout)
method returns true
, that is, if a connection is termed as valid, this validation only guarantees that the server is not unreachable (dead socket). It does not provide any status about the server processes, like whether they are running or not. However, by default, that is, when lightweight connection validation is not enabled, the isValid(timeout)
method does check whether the network between the client and the server is intact or not.
就是说,由于它只校验Oracle主机的连通性,不校验Oracle进程是否运行。如果Oracle进程挂了但主机还在的情况下,这时这个connection会被误判成有效的。
那具体什么情况下会出现问题呢?我能想到的一个场景就是,假如你用一个Oracle集群,有两个节点(节点A在主机1,节点B在主机2),如果节点A因为某些问题挂了,主机1还在,可以连通,程序还会继续尝试跟节点A建立连接,这时程序就会出错了。
参考
https://github.com/brettwooldridge/HikariCP
https://medium.com/@rajchandak1993/understanding-hikaricps-connection-pooling-behaviour-467c5a1a1506