转载

优秀开源代码解析(二)HikariPool(一)初识

优秀开源代码解析(二)HikariPool(一)初识
Java极客  |  作者  /  铿然一叶
这是 Java极客 的第 50 篇原创文章

1、HikariPool是什么

HikariPool是一个开源数据库连接池管理工具,以性能优秀著称。

2、从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() {
            }
复制代码

3、初识HikariPool

HikariPool的代码初看逻辑比较复杂,这里先从如何获取数据库连接开始认识它。获取连接相关的类接口如下:

优秀开源代码解析(二)HikariPool(一)初识

3.1、HikariPool

HikariPool是连接池管理类,负责管理数据库连接。

3.2、ConcurrentBag

ConcurrentBag是一个封装的并发管理工具,负责管理池化资源,不仅仅是数据库连接,其他池化资源都可以通过它来管理。

3.2.1、类定义

// 通过泛型要求其包含的池化资源必须实现IConcurrentBagEntry接口
public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable
复制代码

3.2.2、池化资源接口

// 池化资源接口
   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();
   }
复制代码

3.2.3、获取PoolEntry

public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
复制代码

ConcurrentBag代码比较多,后面将做更详细的介绍。

3.3、PoolEntry

PoolEntry是池化资源的入口类,实现了IConcurrentBagEntry接口,同时一对一持有Connection,方便对Connection管理。其创建连接代理的代码如下:

Connection createProxyConnection(final ProxyLeakTask leakTask, final long now)
   {
      return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
   }
复制代码

3.4、ProxyFactory

ProxyFactory用于获取数据库连接。

3.4.1、得到连接

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监控

3.4.2、JavassistProxyFactory.java

用于替换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");
   }
复制代码

3.5、ProxyConnection

ProxyConnection是个连接代理,持有Connection。

3.6、ProxyLeakTask

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);
      }
   }
}
复制代码

3.7、ProxyLeakTaskFactory

ProxyLeakTaskFactory是个工厂类,用于创建ProxyLeakTask,之所以有这个工厂类,是因为有不同的ProxyLeakTask实现。可以根据配置来决定是否要监控连接泄漏。

ProxyLeakTask schedule(final PoolEntry poolEntry)
   {
      // 根据配置来创建不同的代理泄露监控类
      return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
   }
复制代码

以上,是对HikariPool的初步认识,后面将进一步做更详细的介绍。

原文  https://juejin.im/post/5e80b68ce51d45470c12a249
正文到此结束
Loading...