All Articles

Hikari剖析

一、背景

最近突然对底层原理比较痴迷,本着第一性原理的原则,对号称性能最强Hikari的源码做了深入解读,了解下为什么它能成为目前为止最快的数据库连接池技术。

二、数据库连接池历史

【JDBC的出现】JDBC是java连接数据库的一套JDK API标准,其实在JDK1.1版本就已经提出了,但是它也只提供了最基本的连接数据库方式。
【数据库连接池技术诞生】在应用的性能优化过程中,池化技术是一种通过重复利用资源的技术,因为重复利用可以避免资源重复创建,进而节省性能损耗。在数据库的连接技术上尤为需要这种技术加持,所以就诞生了 各种数据库连接池技术。
【数据库连接池技术历史】
按照诞生顺序:Apache Commons的DBCP—>C3P0->Hikari->Druid
性能方面: Hikari最好,在并发数提升后也没有明显的性能下降。

三、Hikari为什么快

源码解读

  1. 流程图

以下是hikari获取connection的核心流程,可以看出来有多级缓存来获取资源 alt text 2. 关键代码剖析
以下是核心获取connection的处理逻辑,有多级池来保障资源重复利用,threadLocal池尤其高效。

  public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
   {
      // Try the thread-local list first
      final var list = threadList.get();
      for (int i = list.size() - 1; i >= 0; i--) {
         final var entry = list.remove(i);
         @SuppressWarnings("unchecked")
         final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
         if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
            return bagEntry;
         }
      }

      // Otherwise, scan the shared list ... then poll the handoff queue
      final int waiting = waiters.incrementAndGet();
      try {
         for (T bagEntry : sharedList) {
            if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               // If we may have stolen another waiter's connection, request another bag add.
               if (waiting > 1) {
                  listener.addBagItem(waiting - 1);
               }
               return bagEntry;
            }
         }

         listener.addBagItem(waiting);

         timeout = timeUnit.toNanos(timeout);
         do {
            final var start = currentTime();
            final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
            if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               return bagEntry;
            }

            timeout -= elapsedNanos(start);
         } while (timeout > 10_000);

         return null;
      }
      finally {
         waiters.decrementAndGet();
      }
   }

总结

  1. connnection多级缓存,第一级使用了threadlocal获取当前线程缓存的连接,第二级使用了sharedList获取整个池子里的缓存,第三级使用SynchronousQueue同步器来获取新构造的connection,依赖SynchronousQueue的特性完成线程间高效通信。

  2. 使用fastlist移除,尾部遍历,针对数据库连接池的场景效率更高。

  3. 使用javasist技术代理connection,调用栈比继承少。

  4. 尽量使用CAS保证操作线程安全,没有锁阻塞

四、其他关心的问题

  1. 数据库连接池要如何选择?

目前Hikari性能最好,druid次之,不过druid的强项在监控上,所以如果是企业级应用且性能要求不高,还是推荐使用Druid,加入性能要求很高,使用Hikari可以节省很多资源,不过得丰富其监控能力。

  1. 数据库连接池要如何设置?

根据具体服务的CPU核数而定,不需要设置太多的原因是设置的多的话也没有足够的CPU来处理线程,而且对于数据库连接很多操作是IO操作,会阻塞线程,线程太多最后会导致线程频繁切换,带来巨大的性能损耗。
参考公式:连接数 = ((CPU核数 * 2) + 有效磁盘数),

五、参考资料

Hikari官方:关于连接池配置
Hikari官方:为什么Hikari快

Published Jun 11, 2024

Software Engineer from China, currently more attention to the automation field of ai interaction