第四部分-并发编程案例分析4:高性能数据库连接池HikariCP

1.高性能数据库连接池HiKarCP

c3p0,DBCP,Tomcat JDBC Connection Pool,Druid

最火的是Hikaricp

最快的数据库连接池,springboot2.0作为默认数据库连接池

2.数据库连接池

池化资源,避免重量级资源的频繁创建和销毁。数据库连接池就是避免数据库连接频繁创建爱你和销毁。

image

需要时从池子里取,用完,将其归还到池子中

实际工作中,持久层框架来完成数据库额crud

3.HiKariCP性能高秘籍1,使用自定义数据结构FastList

数据库操作之后,需要关闭ResultSet,Statement,Connection
有同学只关connection,为了解决这个问题,就希望自动关闭statement及resultSet

思路?
connection跟踪创建的statement,将statement保存在ArrayList中,

HiKariCP做了什么?用ArrayList太慢。因为arraylist的remove()删除statement时候,有优化余地的

正产一个请求,依次创建6个statement,S1-S6,关闭的时候一般是逆序的S6-S1,而ArrayList的remove()方法是顺序遍历查找,再逆序删除statement的时候使用正向查找,效率就太慢了,
所以进行了优化成逆序查找

image

自定义FastList就是重写remove()方法,删除时候逆序遍历查找。同时get(index)没有对index进行越界检查。提高了性能够。

@Override
   public boolean remove(Object element)
   {
      for (int index = size - 1; index >= 0; index--) {
         if (element == elementData[index]) {
            final int numMoved = size - index - 1;
            if (numMoved > 0) {
               System.arraycopy(elementData, index + 1, elementData, index, numMoved);
            }
            elementData[--size] = null;
            return true;
         }
      }

      return false;
   }
   
@Override
   public T get(int index)
   {
      return elementData[index];
   }   

4.HiKariCP性能高秘籍2,使用自定义数据结构ConcurrentBag

核心思想:使用ThreadLocal 避免部分并发问题

ConcurrentBag关键属性


//用于存储所有的数据库连接
CopyOnWriteArrayList<T> sharedList;
//线程本地存储中的数据库连接
ThreadLocal<List<Object>> threadList;
//等待数据库连接的线程数
AtomicInteger waiters;
//分配数据库连接的工具
SynchronousQueue<T> handoffQueue;

线程池初始化时候,调用add将连接加入ConncurrentBag
(加入共享队列sharedList,如果有线程等待数据库连接,就handoffQueue将新创建的连接分配出去)


//将空闲连接添加到队列
void add(final T bagEntry){
  //加入共享队列
  sharedList.add(bagEntry);
  //如果有等待连接的线程,
  //则通过handoffQueue直接分配给等待的线程
  while (waiters.get() > 0 
    && bagEntry.getState() == STATE_NOT_IN_USE 
    && !handoffQueue.offer(bagEntry)) {
      yield();
  }
}

获取数据库连接borrow方法
1.先看本地存储ThreadLocal是否有空闲,如果有,返回空闲连接
2.本地存储无空闲,共享队列中获取连接
3.共享队列也无空闲连接,请求线程需要等待

(注意:线程本地存储的连接是可以被其他线程窃取的,需要用CAS方法防止重复分配)


T borrow(long timeout, final TimeUnit timeUnit){
  // 先查看线程本地存储是否有空闲连接
  final List<Object> list = threadList.get();
  for (int i = list.size() - 1; i >= 0; i--) {
    final Object entry = list.remove(i);
    final T bagEntry = weakThreadLocals 
      ? ((WeakReference<T>) entry).get() 
      : (T) entry;
    //线程本地存储中的连接也可以被窃取,
    //所以需要用CAS方法防止重复分配
    if (bagEntry != null 
      && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
      return bagEntry;
    }
  }

  // 线程本地存储中无空闲连接,则从共享队列中获取
  final int waiting = waiters.incrementAndGet();
  try {
    for (T bagEntry : sharedList) {
      //如果共享队列中有空闲连接,则返回
      if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
        return bagEntry;
      }
    }
    //共享队列中没有连接,则需要等待
    timeout = timeUnit.toNanos(timeout);
    do {
      final long 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);
    //超时没有获取到连接,返回null
    return null;
  } finally {
    waiters.decrementAndGet();
  }
}

释放连接,requite()
修改连接状态STATE_NOT_IN_USE,查看是否在等待线程,有,分配给线程,没有,保存到ThreadLocal本地存储里


//释放连接
void requite(final T bagEntry){
  //更新连接状态
  bagEntry.setState(STATE_NOT_IN_USE);
  //如果有等待的线程,则直接分配给线程,无需进入任何队列
  for (int i = 0; waiters.get() > 0; i++) {
    if (bagEntry.getState() != STATE_NOT_IN_USE 
      || handoffQueue.offer(bagEntry)) {
        return;
    } else if ((i & 0xff) == 0xff) {
      parkNanos(MICROSECONDS.toNanos(10));
    } else {
      yield();
    }
  }
  //如果没有等待的线程,则进入线程本地存储
  final List<Object> threadLocalList = threadList.get();
  if (threadLocalList.size() < 50) {
    threadLocalList.add(weakThreadLocals 
      ? new WeakReference<>(bagEntry) 
      : bagEntry);
  }
}

5.总结

HikariCP的快
(1)无锁算法
(2)自定义数据结构FastList,解决statement等资源的释放时性能问题
(3)自定义数据结构ConcurrentBag,通过ThreadLocal做了一次预分配,将共享连接池前置加了一个类似缓存的东西。避免了直接竞争共享资源。
(4)为什么本地连接会被窃取?ThreadLocal里没空闲的,会去shardList里取NOT_IN_USE的连接,这个连接可以已经在其他ThreadLocal里存在了,可能会出现线程T2从shardList里取到了T1存在ThreadLocal里的还没使用的连接。

原创:做时间的朋友
原文地址:https://www.cnblogs.com/PythonOrg/p/14902834.html