范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

SpringBoot2。0中HikariCP数据库连接池原理解析

  写在前面
  金三银四招聘季就快到了,为助力有求职意向的读者大大获得一份优质职位,小编将每天从Freemen App挑选一个优质职位放在文末,每天定时更新,有求职意向的小伙伴每天可以关注下哈。
  作为后台服务开发,在日常工作中我们天天都在跟数据库打交道,一直在进行各种CRUD操作,都会使用到数据库连接池。按照发展历程,业界知名的数据库连接池有以下几种:c3p0、DBCP、Tomcat JDBC Connection Pool、Druid 等,不过最近最火的是 HiKariCP。
  HiKariCP 号称是业界跑得最快的数据库连接池,自从 SpringBoot 2.0 将其作为默认数据库连接池后,其发展势头锐不可当。那它为什么那么快呢?今天咱们就重点聊聊其中的原因。
  一、什么是数据库连接池
  在讲解HiKariCP之前,我们先简单介绍下什么是数据库连接池(Database Connection Pooling),以及为什么要有数据库连接池。
  从根本上而言,数据库连接池和我们常用的线程池一样,都属于池化资源,它在程序初始化时创建一定数量的数据库连接对象并将其保存在一块内存区中。它允许应用程序重复使用一个现有的数据库连接,当需要执行 SQL 时,我们是直接从连接池中获取一个连接,而不是重新建立一个数据库连接,当 SQL 执行完,也并不是将数据库连接真的关掉,而是将其归还到数据库连接池中。我们可以通过配置连接池的参数来控制连接池中的初始连接数、最小连接、最大连接、最大空闲时间等参数,来保证访问数据库的数量在一定可控制的范围类,防止系统崩溃,同时保证用户良好的体验。数据库连接池示意图如下所示:
  因此使用数据库连接池的核心作用,就是避免数据库连接频繁创建和销毁,节省系统开销。因为数据库连接是有限且代价昂贵,创建和释放数据库连接都非常耗时,频繁地进行这样的操作将占用大量的性能开销,进而导致网站的响应速度下降,甚至引起服务器崩溃。
  二、常见数据库连接池对比分析
  这里详细总结了常见数据库连接池的各项功能比较,我们重点分析下当前主流的阿里巴巴Druid与HikariCP,HikariCP在性能上是完全优于Druid连接池的。而Druid的性能稍微差点是由于锁机制的不同,并且Druid提供更丰富的功能,包括监控、sql拦截与解析等功能,两者的侧重点不一样,HikariCP追求极致的高性能。
  下面是官网提供的性能对比图,在性能上面这五种数据库连接池的排序如下:HikariCP>druid>tomcat-jdbc>dbcp>c3p0:
  三、HikariCP 数据库连接池简介
  HikariCP 号称是史上性能最好的数据库连接池,SpringBoot 2.0将它设置为默认的数据源连接池。Hikari相比起其它连接池的性能高了非常多,那么,这是怎么做到的呢?通过查看HikariCP官网介绍,对于HikariCP所做优化总结如下:
  1. 字节码精简 :优化代码,编译后的字节码量极少,使得CPU缓存可以加载更多的程序代码;
  HikariCP在优化并精简字节码上也下了功夫,使用第三方的Java字节码修改类库Javassist来生成委托实现动态代理.动态代理的实现在ProxyFactory类,速度更快,相比于JDK Proxy生成的字节码更少,精简了很多不必要的字节码。
  2. 优化代理和拦截器:减少代码,例如HikariCP的Statement proxy只有100行代码,只有BoneCP的十分之一;
  3. 自定义数组类型(FastStatementList)代替ArrayList:避免ArrayList每次get()都要进行range check,避免调用remove()时的从头到尾的扫描(由于连接的特点是后获取连接的先释放);
  4. 自定义集合类型(ConcurrentBag):提高并发读写的效率;
  5. 其他针对BoneCP缺陷的优化,比如对于耗时超过一个CPU时间片的方法调用的研究。
  当然作为一个数据库连接池,不能说快就会被消费者所推崇,它还具有非常好的健壮性及稳定性。HikariCP从15年推出以来,已经经受了广大应用市场的考验,并且成功地被SpringBoot2.0作为默认数据库连接池进行推广,在可靠性上面是值得信任的。其次借助于其代码量少,占用cpu和内存量小的优点,使得它的执行率非常高。最后,Spring配置HikariCP和druid基本没什么区别,迁移过来非常方便,这些都是为什么HikariCP目前如此受欢迎的原因。
  四、HikariCP 核心源码解析
  4.1 FastList 是如何优化性能问题的
  首先我们来看一下执行数据库操作规范化的操作步骤: 通过数据源获取一个数据库连接; 创建 Statement; 执行 SQL; 通过 ResultSet 获取 SQL 执行结果; 释放 ResultSet; 释放 Statement; 释放数据库连接。
  当前所有数据库连接池都是严格地根据这个顺序来进行数据库操作的,为了防止最后的释放操作,各类数据库连接池都会把创建的 Statement 保存在数组 ArrayList 里,来保证当关闭连接的时候,可以依次将数组中的所有 Statement 关闭。HiKariCP 在处理这一步骤中,认为 ArrayList 的某些方法操作存在优化空间,因此对List接口的精简实现,针对List接口中核心的几个方法进行优化,其他部分与ArrayList基本一致 。
  首先是get()方法,ArrayList每次调用get()方法时都会进行rangeCheck检查索引是否越界,FastList的实现中去除了这一检查,是因为数据库连接池满足索引的合法性,能保证不会越界,此时rangeCheck就属于无效的计算开销,所以不用每次都进行越界检查。省去频繁的无效操作,可以明显地减少性能消耗。 FastList get()操作 public T get(int index) {    // ArrayList 在此多了范围检测 rangeCheck(index);    return elementData[index]; }
  其次是remove方法,当通过 conn.createStatement() 创建一个 Statement 时,需要调用 ArrayList 的 add() 方法加入到 ArrayList 中,这个是没有问题的;但是当通过 stmt.close() 关闭 Statement 的时候,需要调用 ArrayList 的 remove() 方法来将其从 ArrayList 中删除,而ArrayList的remove(Object)方法是从头开始遍历数组,而FastList是从数组的尾部开始遍历,因此更为高效。
  假设一个 Connection 依次创建 6 个 Statement,分别是 S1、S2、S3、S4、S5、S6,而关闭 Statement 的顺序一般都是逆序的,从S6 到 S1,而 ArrayList 的 remove(Object o) 方法是顺序遍历查找,逆序删除而顺序查找,这样的查找效率就太慢了。因此FastList对其进行优化,改成了逆序查找。如下代码为FastList 实现的数据移除操作,相比于ArrayList的 remove()代码, FastList 去除了检查范围 和 从头到尾遍历检查元素的步骤,其性能更快。
  FastList 删除操作 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);          }          //如果角标是最后面的 直接初始化为null          elementData[--size] = null;          return true;       }    }    return false; }
  通过上述源码分析,FastList 的优化点还是很简单的。相比ArrayList仅仅是去掉了rage检查,扩容优化等细节处,删除时数组从后往前遍历查找元素等微小的调整,从而追求性能极致。当然FastList 对于 ArrayList 的优化,我们不能说ArrayList不好。所谓定位不同、追求不同,ArrayList作为通用容器,更追求安全、稳定,操作前rangeCheck检查,对非法请求直接抛出异常,更符合 fail-fast(快速失败)机制,而FastList追求的是性能极致。
  下面我们再来聊聊 HiKariCP 中的另外一个数据结构 ConcurrentBag,看看它又是如何提升性能的。
  4.2 ConcurrentBag 实现原理分析
  当前主流数据库连接池实现方式,大都用两个阻塞队列来实现。一个用于保存空闲数据库连接的队列 idle,另一个用于保存忙碌数据库连接的队列 busy;获取连接时将空闲的数据库连接从 idle 队列移动到 busy 队列,而关闭连接时将数据库连接从 busy 移动到 idle。这种方案将并发问题委托给了阻塞队列,实现简单,但是性能并不是很理想。因为 Java SDK 中的阻塞队列是用锁实现的,而高并发场景下锁的争用对性能影响很大。
  HiKariCP 并没有使用 Java SDK 中的阻塞队列,而是自己实现了一个叫做 ConcurrentBag 的并发容器,在连接池(多线程数据交互)的实现上具有比LinkedBlockingQueue和LinkedTransferQueue更优越的性能。
  ConcurrentBag 中最关键的属性有 4 个,分别是:用于存储所有的数据库连接的共享队列 sharedList、线程本地存储 threadList、等待数据库连接的线程数 waiters 以及分配数据库连接的工具 handoffQueue。其中,handoffQueue 用的是 Java SDK 提供的 SynchronousQueue,SynchronousQueue 主要用于线程之间传递数据。 ConcurrentBag 中的关键属性 // 存放共享元素,用于存储所有的数据库连接 private final CopyOnWriteArrayList sharedList; // 在 ThreadLocal 缓存线程本地的数据库连接,避免线程争用 private final ThreadLocal> threadList; // 等待数据库连接的线程数 private final AtomicInteger waiters; // 接力队列,用来分配数据库连接 private final SynchronousQueue handoffQueue;
  ConcurrentBag 保证了全部的资源均只能通过 add() 方法进行添加,当线程池创建了一个数据库连接时,通过调用 ConcurrentBag 的 add() 方法加入到 ConcurrentBag 中,并通过 remove() 方法进行移出。下面是 add() 方法和 remove() 方法的具体实现,添加时实现了将这个连接加入到共享队列 sharedList 中,如果此时有线程在等待数据库连接,那么就通过 handoffQueue 将这个连接分配给等待的线程。 ConcurrentBag 的 add() 与 remove() 方法 public void add(final T bagEntry) {    if (closed) {       LOGGER.info("ConcurrentBag has been closed, ignoring add()");       throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()");    }    // 新添加的资源优先放入sharedList    sharedList.add(bagEntry);      // 当有等待资源的线程时,将资源交到等待线程 handoffQueue 后才返回    while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {       yield();    } } public boolean remove(final T bagEntry) {    // 如果资源正在使用且无法进行状态切换,则返回失败    if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) {       LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry);       return false;    }    // 从sharedList中移出    fin查看线程本地存储 threadList 中是否有空闲连接,如果有,则返回一个空闲的连接;  如果线程本地存储中无空闲连接,则从共享队列 sharedList 中获取;  如果共享队列中也没有空闲的连接,则请求线程需要等待。al boolean removed = sharedList.remove(bagEntry);    if (!removed && !closed) {       LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry);    }    return removed; }
  同时ConcurrentBag通过提供的 borrow() 方法来获取一个空闲的数据库连接,并通过requite()方法进行资源回收,borrow() 的主要逻辑是: 查看线程本地存储 threadList 中是否有空闲连接,如果有,则返回一个空闲的连接;  如果线程本地存储中无空闲连接,则从共享队列 sharedList 中获取;  如果共享队列中也没有空闲的连接,则请求线程需要等待。
  ConcurrentBag 的 borrow() 与 requite() 方法 // 该方法会从连接池中获取连接, 如果没有连接可用, 会一直等待timeout超时 public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {    // 首先查看线程本地资源threadList是否有空闲连接    final List list = threadList.get();    // 从后往前反向遍历是有好处的, 因为最后一次使用的连接, 空闲的可能性比较大, 之前的连接可能会被其他线程提前借走了    for (int i = list.size() - 1; i >= 0; i--) {       final Object entry = list.remove(i);       @SuppressWarnings("unchecked")       final T bagEntry = weakThreadLocals ? ((WeakReference) entry).get() : (T) entry;       // 线程本地存储中的连接也可以被窃取, 所以需要用CAS方法防止重复分配       if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {          return bagEntry;       }    }    // 当无可用本地化资源时,遍历全部资源,查看可用资源,并用CAS方法防止资源被重复分配    final int waiting = waiters.incrementAndGet();    try {       for (T bagEntry : sharedList) {          if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {             // 因为可能"抢走"了其他线程的资源,因此提醒包裹进行资源添加             if (waiting > 1) {                listener.addBagItem(waiting - 1);             }             return bagEntry;          }       }         listener.addBagItem(waiting);       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);       return null;    }    finally {       waiters.decrementAndGet();    } }   public 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 threadLocalList = threadList.get();    if (threadLocalList.size() < 50) {       threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);    } }
  borrow() 方法可以说是整个 HikariCP 中最核心的方法,它是我们从连接池中获取连接的时候最终会调用到的方法。需要注意的是 borrow() 方法只提供对象引用,不移除对象,因此使用时必须通过 requite() 方法进行放回,否则容易导致内存泄露。requite() 方法首先将数据库连接状态改为未使用,之后查看是否存在等待线程,如果有则分配给等待线程;否则将该数据库连接保存到线程本地存储里。
  ConcurrentBag 实现采用了queue-stealing的机制获取元素:首先尝试从ThreadLocal中获取属于当前线程的元素来避免锁竞争,如果没有可用元素则再次从共享的CopyOnWriteArrayList中获取。此外,ThreadLocal和CopyOnWriteArrayList在ConcurrentBag中都是成员变量,线程间不共享,避免了伪共享(false sharing)的发生。同时因为线程本地存储中的连接是可以被其他线程窃取的,在共享队列中获取空闲连接,所以需要用 CAS 方法防止重复分配。
  五、总结
  本文首先对为什么使用数据库连接池,以及常见的数据库连接池的功能及性能进行了简单介绍,通过分析HiKariCP官网介绍及其源码,可以发现HiKariCP主要通过对字节码进行精简、优化代理和拦截器、自定义数组类型 FastList 及自定义并发集合类型 ConcurrentBag 等内容进行优化,文中重点讲解了FastList 与ConcurrentBag 的优化原理(FastList 适用于逆序删除场景;而 ConcurrentBag 本质上是通过 ThreadLocal 将连接池中的连接按照线程做一次预分配,避免直接竞争共享资源,减少并发CAS带来的CPU CACHE的频繁失效,从而提高性能,非常适合池化资源的分配),达到显著提升数据库连接池性能的效果。需要注意的是threadLocal可能带来连接池关闭时引用还存在的情况,有可能导致内存泄露,因此一定要使用requite()方法来进行资源回收处理。
  Hikari 作为 SpringBoot2.0默认的连接池,目前在行业内使用范围非常广,对于大部分业务来说,都可以实现快速接入使用,做到高效连接。
  参考资料  https://github.com/brettwooldridge/HikariCP https://github.com/alibaba/druid
  本文转载自vivo互联网技术
  今日职位推荐:
  数据分析岗
  熟悉sqlserver ,精通sql是最好的
  熟悉 powerbi,熟练使用DAX 函数
  熟悉SSIS(DTS)
  有独立负责项目架构经验,项目上线,部署,维护等经验
  工作地点:上海
  薪资范围:20k-22k
  投递方式:Freemen App中定位上海搜索数据分析
  祝愿IT互联网行业越来越好
百香果的壳能吃吗?怎么吃?可以吃的。百香果皮的食用并不是说直接食用,因为百香果的皮比较硬,所以直接吃到肚子里也消化不了。百香果的果壳可用于提取果胶,是食品加工的优良稳定剂和增稠剂。还可以入药,特别是一直频繁肥肉熬成油渣来吃是不是就不容易长胖了?在我父母辈的口中猪油渣可是他们小时候难得的零食,只要家里熬猪油,小孩子都不出去疯跑着玩了,都乖乖守在家里厨房门口,就为了能第一时间得到浓香酥脆的猪油渣吃。不过猪油渣这个东西到底健不同样花5999元,为什么感觉大多数人不选小米11Ultra,而选iPhone13呢?我觉得用小米11Ultra和iPhone13来进行PK,这对于小米是很不公平的!不是不选,而是很多人知道小米11Ultra的少,而对于iPhone13可以说品牌认知感更强一点!因为为什么有的老司机每次加油都只加200,而不是油箱加满?汽车加200和加满油这里面暗藏学问,老司机加油总能省钱,而新手却伤车,真是隔行如隔山,加油这事原本很简单,但却有很多门道,如果没有搞懂这其中的问题,不仅会增加油耗,还可能毁车。经常苹果手机怎样验机最可靠?谢谢头条君邀请!关于楼主问的这个如何检测苹果手机最靠谱,其实行内来说外观可以翻新,主板可以美容,手机数据也可以经过改写硬盘数据来作假,普通人压根没有办法识别机子本身的好坏。很多人会西藏绘就人水和谐生态画卷早春的高原,沉睡了一冬的湖泊开始解冻,冰川雪水带着春天的气息向东流淌,和煦的春风抚过河谷草甸,藏东南的桃花开得正艳行走在高原,处处呈现着一幅蓝天白云水清岸绿生态和美的生态画卷。西藏济南春光好泉城春游指南发布第一期春游正当时踏青赏花露营采摘来源海报新闻大众网海报新闻记者李金珊济南报道清明将至,气温大幅回升,正是春游的绝佳时机。为满足市民和游客的出游需求,济南市文化和旅游局组织各县区文旅部门和企业以济南春光好为主题推出有你家吗?现代版富春山居图!尤溪这里美了!靓了尤溪县乡村五个美丽建设展播乡村五美齐发力,打造尤溪美丽乡村美丽经济美丽生活三美融合发展之路,全面推进乡村振兴2023年,尤溪县认真贯彻省委市委工作部署,结合尤溪县五大一重攻坚冲刺年邓刚回国首播讲泰国游钓,去一次就后悔,各种套路把钓鱼人耍着玩前段时间邓刚大师是跑去泰国游钓,有粉丝是发现自从发布了那个钓虾视频后,已经很久没消息,纷纷猜测他是不是出什么事了。近日邓刚是开了一个直播,表示自己已经在国内了,之所以这个系列只拍了百泉湖黑天鹅喜添四小萌宝3月24日,记者从辉县市了解到,百泉景区黑天鹅家族近日再添4只小成员,总量达到了16只。小萌宝已经成为网红。百泉湖内,只见4只圆滚滚的小黑天鹅在宝爸宝妈的陪伴下,在湖内戏水觅食,悠中年大叔可以穿小脚裤工装裤吗?我就喜欢小脚工装裤!看看我53岁了穿的还算潮吧?男人到了中年,完全可以试试穿工装裤,搭配马丁靴,男性荷尔蒙爆棚,还可以显得年轻,保暖性也不错。工装裤现在又有点流行了,连公认的潮流教
小米汽车工程车已完成无意代工和收购坚持自建工厂前不久,雷军在2022年度演讲上,正式公布了小米汽车的自动驾驶技术测试视频,展示了非常完善的功能,甚至还能实现自动充电。自此,小米汽车的各种动态也备受关注,随后还传出小米与北汽集团QQ推倒重做,全新版本内测上线众所周知,QQ和微信,是腾讯的两大杀手级应用。但不同的是,微信的用户依然在持续增长,QQ却有点老态龙钟,很多人已经不用了。虽然QQ用户在不断流失,但鉴于老本够厚,QQ依然拥有数亿级花一个月工资,入手vivoX80Pro,不吹不黑,聊聊2个月使用感受头条创作挑战赛相信对于喜欢拍照的摄影人群来说,如果预算比较多的话,他们都会买入手一台拍照表现出色的影像旗舰机型,相机手机配合使用,事半功倍。作为数码创作者,我个人首推就是vivoX中国移动通信官网赠送流量活动是巨坑我在移动官网看到赠送65G流量体验包活动,就参与了流量确认送到,但是我上网后却产生了流量费用咨询10086后被通知,此体验流量包是根据移动公司系统是否认定来进行的,也就是说你用流量是时候重估百度了文丨黄芳华出品丨牛刀财经(niudaocaijing)人工智能甚至有了方向性的改变,这是李彦宏的判断。2022年世界人工智能大会上,李彦宏发表了主题为人工智能和实体经济的双向奔赴的以前的苹果一分价钱一分货,现在的苹果除了芯片屏幕以外哪都抠锁看了看配置表对比同期安卓,以iPhonese为例,同期安卓旗舰没夜景模式,底大小基本上12。8寸徘徊(imx234),像素12001600万,hdr合成慢的要死。苹果底imx315销售必备安卓苹果通话记录生成app下载链接这里httpwww。thjlplsc。top复制到浏览器打开花了半年,在搜索引擎上搜索了这么多结果,结果都是做广告的,前面有的都是讲废话我去搜索了iphone通话记录生成伪数字时代如何推动数实融合?业界支招建言光明网讯(记者李政葳)数字经济发展速度之快辐射范围之广影响程度之深前所未有,正推动生产方式生活方式和治理方式深刻变革。在8月30日举办的数字产业集群专题研讨会暨中国数实融合50人论美国下狠手了,禁止对华出口高级芯片,美企警告离不开中国市场近日来,美国为了遏制中国的发展,实现在芯片等高科技领域与中国脱钩的目的,已要求英伟达和AMD公司这两家美国芯片公司停止对中国出口高级的计算机芯片。如果不是考虑到中国自身已具备一定的机构三星Q2在全球NAND闪存市场销售额环比下滑5。4机构三星Q2在全球NAND闪存市场销售额环比下滑5。4科创板日报2日讯,据研究机构的报告显示,今年二季度三星电子NAND闪存市场的销售额环比下滑5。4,为59。8亿美元。所占的份额我以为旗舰机再也回不到1999了!这次摩托罗拉S30Pro要为发烧而生智能手机圈就应该有位发烧友呐喊的品牌,我记得在早以前,智能手机市场价格普遍在三四千时,突然有个品牌喊出为发烧而生,带来了一款配置顶级,价格却足以震撼整个手机圈的1999元,让无数烧