zoukankan      html  css  js  c++  java
  • 第四部分-并发编程案例分析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里的还没使用的连接。

    原创:做时间的朋友
  • 相关阅读:
    高并发 内核优化
    mysql 读写分离
    Jmeter使用入门
    Jenkins+Jmeter+Ant接口持续集成
    Android客户端稳定性测试——Monkey
    SVN客户端项目递归删除.svn目录
    Windows 下 php5+apache+mysql5 手工搭建笔记
    熟悉常用的Linux操作
    C语言文法
    词法分析实验报告
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14902834.html
Copyright © 2011-2022 走看看