HikariPool是一个开源数据库连接池管理工具,以性能优秀著称。
1.HikariPool用到很多并发和线程管理工具,可以学习它们的用法。
2.有不少提升性能的用法,可以借鉴。
3.用到了一些Java的特性,使得代码看起来更友好,简洁,例如:
1)int的定义
// 以下这个定义是合法的,可用_来表示千分位,方便识别具体值是多少,而不用一个个的数 int i = 10_000; 复制代码
2)通过Lambda来简化内部类的定义,例如HikariPool.java中的如下代码:
closeConnectionExecutor.execute(() -> { quietlyCloseConnection(connection, closureReason); if (poolState == POOL_NORMAL) { fillPool(); } }); 复制代码
不用Lambda表达式则为如下写法:
closeConnectionExecutor.execute(new Runnable() { @Override public void run() { quietlyCloseConnection(connection, closureReason); if (poolState == POOL_NORMAL) { fillPool(); } } }); 复制代码
因为Runnable只有一个方法,因此可通过
() -> { 复制代码
替换如下代码,使得代码更加简洁:
new Runnable() { @Override public void run() { } 复制代码
HikariPool的代码初看逻辑比较复杂,这里先从如何获取数据库连接开始认识它。获取连接相关的类接口如下:
HikariPool是连接池管理类,负责管理数据库连接。
ConcurrentBag是一个封装的并发管理工具,负责管理池化资源,不仅仅是数据库连接,其他池化资源都可以通过它来管理。
// 通过泛型要求其包含的池化资源必须实现IConcurrentBagEntry接口 public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable 复制代码
// 池化资源接口 public interface IConcurrentBagEntry { // 池化资源的状态定义 int STATE_NOT_IN_USE = 0; int STATE_IN_USE = 1; int STATE_REMOVED = -1; int STATE_RESERVED = -2; // 通过CAS操作而非锁来提高并发效率 boolean compareAndSet(int expectState, int newState); void setState(int newState); int getState(); } 复制代码
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException 复制代码
ConcurrentBag代码比较多,后面将做更详细的介绍。
PoolEntry是池化资源的入口类,实现了IConcurrentBagEntry接口,同时一对一持有Connection,方便对Connection管理。其创建连接代理的代码如下:
Connection createProxyConnection(final ProxyLeakTask leakTask, final long now) { return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit); } 复制代码
ProxyFactory用于获取数据库连接。
static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } 复制代码
1.这个方法内部抛出了异常,实际方法体会被JavassistProxyFactory替换。
2.这里返回ProxyConnection而不是Connection会更灵活,方便实现数据库监控。 关于数据库监控参考: Java调用链跟踪关键技术(四)SQL监控
用于替换ProxyFactory类的方法体。
private static void modifyProxyFactory() throws NotFoundException, CannotCompileException, IOException { System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory"); String packageName = ProxyConnection.class.getPackage().getName(); CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory"); for (CtMethod method : proxyCt.getMethods()) { switch (method.getName()) { // 这里对getProxyConnection方法体内容做了替换 case "getProxyConnection": method.setBody("{return new " + packageName + ".HikariProxyConnection($$);}"); break; case "getProxyStatement": method.setBody("{return new " + packageName + ".HikariProxyStatement($$);}"); break; case "getProxyPreparedStatement": method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($$);}"); break; case "getProxyCallableStatement": method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($$);}"); break; case "getProxyResultSet": method.setBody("{return new " + packageName + ".HikariProxyResultSet($$);}"); break; case "getProxyDatabaseMetaData": method.setBody("{return new " + packageName + ".HikariProxyDatabaseMetaData($$);}"); break; default: // unhandled method break; } } // 替换后的class直接放到classes目录下 proxyCt.writeFile(genDirectory + "target/classes"); } 复制代码
ProxyConnection是个连接代理,持有Connection。
ProxyLeakTask用于监控数据库连接是否泄露,监控思路是:
1.在创建连接时通过ScheduledExecutorService延迟执行ProxyLeakTask,这个延迟执行的时间为配置的leakDetectionThreshold,如果从创建连接代理开始到超过leakDetectionThreshold还没有关闭连接代理,ProxyLeakTask就会被执行,一旦执行就说明连接代理可能泄露了。
2.如果在leakDetectionThreshold时间内连接代理被关闭,则ProxyLeakTask会被cancel,这样就不会被执行。
class ProxyLeakTask implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class); static final ProxyLeakTask NO_LEAK; // 用于延迟调度ProxyLeakTask private ScheduledFuture<?> scheduledFuture; private String connectionName; private Exception exception; private String threadName; private boolean isLeaked; static { // 不需要监控连接泄露的ProxyLeakTask的实现类 NO_LEAK = new ProxyLeakTask() { @Override void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {} @Override public void run() {} // 默认啥都不做 @Override public void cancel() {} // 默认啥都不做 }; } ProxyLeakTask(final PoolEntry poolEntry) { this.exception = new Exception("Apparent connection leak detected"); this.threadName = Thread.currentThread().getName(); this.connectionName = poolEntry.connection.toString(); } private ProxyLeakTask() { } void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) { // 通过超过leakDetectionThreshold时间后,延迟调用ProxyLeakTask,来报告泄漏信息 scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS); } /** {@inheritDoc} */ @Override // 一旦被执行,说明获取连接到关闭超过了leakDetectionThreshold时间 public void run() { isLeaked = true; final StackTraceElement[] stackTrace = exception.getStackTrace(); final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5]; System.arraycopy(stackTrace, 5, trace, 0, trace.length); exception.setStackTrace(trace); // 下面是监控到连接泄漏的处理,这里只是记录到日志中,如果通过一个接口处理,并可以让使用者动态实现会更灵活 LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception); } void cancel() { scheduledFuture.cancel(false); if (isLeaked) { // 检查到泄漏后连接被关闭,则给一个提示信息 LOGGER.info("Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)", connectionName, threadName); } } } 复制代码
ProxyLeakTaskFactory是个工厂类,用于创建ProxyLeakTask,之所以有这个工厂类,是因为有不同的ProxyLeakTask实现。可以根据配置来决定是否要监控连接泄漏。
ProxyLeakTask schedule(final PoolEntry poolEntry) { // 根据配置来创建不同的代理泄露监控类 return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry); } 复制代码
以上,是对HikariPool的初步认识,后面将进一步做更详细的介绍。