• Hikari使用配置与源码分析


    一、SpringBoot2.x 默认连接池 HikariCP简单配置

    现在国内用的最多的数据库连接池无疑是druid,因为它的监控功能实在太好用了,另外性能、稳定性、社区活跃度等各方面几乎没啥大的缺点。我们公司自然也是用的druid,这导致我一直没意识到springboot默认的连接池的存在。直到今天,我新建了一个springboot项目,导入jpa和web依赖包,配置好mysql地址,发现数据库连接竟然失败了。

    我发现日志里有打印

    MyHikariCP - Starting...

    然后嘛,就是在启动这个连接池的过程中报错了。带着好奇心百度了下这个是啥玩意儿,一查才知道,这个是springboot2.x默认的数据库连接池,而springboot1.x默认的是tomcat连接池

    于是我在网上找了份HikariCP的标准配置文件,只需改下数据库名和账号密码就能正常使用了。

    ## 数据库配置
    spring.datasource.type=com.zaxxer.hikari.HikariDataSource
    spring.datasource.driverClassName = com.mysql.jdbc.Driver
    spring.datasource.url = jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.datasource.username = root
    spring.datasource.password = root
    ##  Hikari 连接池配置 ------ 详细配置请访问:https://github.com/brettwooldridge/HikariCP
    ## 最小空闲连接数量
    spring.datasource.hikari.minimum-idle=5
    ## 空闲连接存活最大时间,默认600000(10分钟)
    spring.datasource.hikari.idle-timeout=180000
    ## 连接池最大连接数,默认是10
    spring.datasource.hikari.maximum-pool-size=10
    ## 此属性控制从池返回的连接的默认自动提交行为,默认值:true
    spring.datasource.hikari.auto-commit=true
    ## 连接池母子
    spring.datasource.hikari.pool-name=MyHikariCP
    ## 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
    spring.datasource.hikari.max-lifetime=1800000
    ## 数据库连接超时时间,默认30秒,即30000
    spring.datasource.hikari.connection-timeout=30000
    spring.datasource.hikari.connection-test-query=SELECT 1

    二、HikariCP源码分析

    HikariCP使用Javassist字节码操作库来实现动态代理,优化并精简了字节码,同时内部使用com.zaxxer.hikari.util.FastList代替了ArrayList,使用了更好的并发集合类com.zaxxer.hikari.util.ConcurrentBag,号称是目前最快的数据库连接池。

    1. HikariPool和PoolEntry

    HikariPool有三个内部类,其中一个是自定义的运行时异常,暂且不谈。另两个分别是HouseKeeper和PoolEntryCreater。HouseKeeper是一个Runnable线程,PoolEntryCreator是一个Callable执行单元。Hikari在自己的构造方法里使用了这两个线程类。

    public HikariPool(){
    
      this.poolEntryCreator = new HikariPool.PoolEntryCreator((String)null);
    
      this.houseKeepingExecutorService = this.initializeHouseKeepingExecutorService();
    
      this.houseKeeperTask = this.houseKeepingExecutorService.scheduleWithFixedDelay(new HikariPool.HouseKeeper(), 100L, this.housekeepingPeriodMs, TimeUnit.MILLISECONDS);
    
    }

    先看下HikariPool的getConnection方法,里面调用了poolEntry.createProxyConnection(),poolEntry是poolEntryCreator创造出来放到ConcurrentBag<PoolEntry>里的,这里取出来。

    再来看createProxyConnection

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

    终于走到ProxyFactory这里,具体看下面有关hikari工厂和代理的讲解。

    2. ProxyFactory

    代码简化下大致是这样:

    public final class ProxyFactory{
          static ProxyConnection getProxyConnection(){return new HikariProxyConnection()}; 
    static Statement getProxyStatement(){return new
    HikariProxyStatement()};
    static CallableStatement getProxyCallableStatement(){return new HikariProxyCallableStatement()};
    static PreparedStatement getProxyPreparedStatement(){return new HikariProxyPreparedStatement()};
    static ResultSet getProxyResultSet(){return new HikariProxyResultSet()};
    static DatabaseMetaData getProxyDatabaseMetaData(){return new HikariProxyDatabaseMetaData()};
    }

    因为只有一个具体工厂类,里面的工厂方法也都是静态的,所以这是一个典型的简单工厂模式。

    ProxyFactory生产的是Hikari有关数据库操作相关的代理类,被代理的类是java.sql下面数据库操作的原生类。

    3. hikari中的动态代理

    我们用ProxyConnection这个代理类来举例,这里只贴出部分代码

    public abstract class ProxyConnection implements Connection {
    
    protected ProxyConnection(Connection connection) {
            this.delegate = connection;
        }
    }

    可以看到ProxyConnection实现了共用主题接口Connection,并且通过构造方法持有另一个Connection实际对象的引用。这里另一个Connection实际对方就是被代理的类。

    这里使用了JavassistProxyFaxctory动态代理,是预先加载字节码的,涉及到类加载机制,我还没看懂,以后慢慢看。

    4. fastlist对比arraylist

    总结:因为fastlist仅供内部使用,所以减少了很多安全校验。另外fastlist因为确认马上就会使用,直接初始化长度32的数组,扩容也改成2倍再扩容,减少了扩容次数。

    这位大佬讲的很全面,从构造函数、扩容、插入、删除、取值、迭代器各方面做了对比。

    读HikariCP源码学Java(二)—— 因地制宜的改装版ArrayList:FastList - 缪若尘 - 博客园 (cnblogs.com)

    5. ConcurrentBag

    这个是借鉴了c#中的设计,主要有如下特点

    1. A lock-free design
    2. ThreadLocal caching
    3. Queue-stealing
    4. Direct hand-off optimizations

    最后,推荐下这个大佬写的追光者系列Hikari三连,感觉写得很好,我自己也还没看完,mark一下有空再看。

    【追光者系列】HikariCP源码分析之字节码修改类库Javassist委托实现动态代理 - 云+社区 - 腾讯云 (tencent.com)

    三、简单看下druid源码

    大概了看了下druid没有类似hikari里那么明显的pool类,它存放连接的容器叫DruidConnectionHolder。druid核心代码主要在DuidDataSource,这个类是简单工厂DruidDataSourceFactory的产品。那么着重看下DruidDataSource,它的父类实现了java.sql.DataSource接口,所以它本质也是DataSource的一个实现类。

    结合druid使用时的配置来看,会更好理解一点   

        initialSize: 5
    minIdle:
    5 # 初始化大小,最小,最大 maxActive: 20 maxWait: 60000 # 配置获取连接等待超时的时间 timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 maxPoolPreparedStatementPerConnectionSiz: 20 filters: stat,wall,log4j connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录

    1. 先看DruidDataSource的构造方法

    默认是非公平锁(效率高),也可以使用有参构造函数传入false设置为公平锁。构造函数中创建计算器值为2的全局CountDownLatch。

    2. 然后看DruidDataSource对外提供的入口函数getConnection(long maxWaitMillis)

    这个参数表示连接不使用后最大等待时间。函数内部首先调用init()函数进行了DruidDataSource的初始化

    1)异步初始化

    init中判断如果asyncInit为true,也就是异步初始化,则根据使用for循环去创建initialSize数量的CreateConnectionTask线程,并将线程提交到Future中去运行。

    2)同步初始化

    如果asyncInit为false,则使用while语句判断poolingCount是否小于initialSize,如果小于,则直接调用父类的createPhysicalConnection()创建一个连接,并放入holder容器。

    3)创建守护线程

    紧接着init()函数会创建LogStatsThread、CreateConnectionThread、DestroyConnectionThread这三个守护线程,其中LogStatsThread不会使用CountdownLatch。这时候调用全局CountDownLatch的await函数,等待上面几个守护进程创建的核心连接创建完成。

    3. DruidDataSource有5个内部类

    LogStatsThread  继承Thread类,设置为守护线程,功能是打印日志。

    CreateConnectionThread  继承Thread类,设置为守护线程。在run()方法的第一步会调用countDown(),只会执行一次。下面会在while死循环中判断是否需要创建新连接,如果满足条件会调用createPhysicalConnection()。

    DestroyConnectionThread  继承Thread类,设置为守护线程。在run()方法的第一步会调用countDown(),只会执行一次。下面会在while死循环中判断是否需要销毁连接,如果满足条件,调用destoryTask的run()方法。

    CreateConnectionTask   实现Runnable接口,核心方法runInternal() ,如果没有等待使用的闲置连接(这个判断过程用ReetrantLock保证同步安全),则创建连接。创建连接的方法createPhysicalConnection()是DruidDataSource的父类中定义的方法。另外这里面有一个fastfail快速失败机制以后有空再研究。

    DestroyTask   实现Runnable接口,run方法中移除超时的连接。

    4. DruidConnectionHolder

    5. druid中的过滤器链

    用来实现监控、日志、防sql注入,分别对应参数 filters: stat,log4j,wall

    6. DruidDataSourceC3P0Adapter

    这个适配器用来适配c3p0的连接,具体还没细看。hikari中貌似没看到这种适配设计。

    7. ConnectionProxyImpl

    这个是druid中的代理类,被代理的是connection各个开发商具体的实现类。这个我是看其它源码分析文章看到的,先记录在这儿,后面继续深入看druid源码的时候再慢慢看。

  • 相关阅读:
    CentOS6.9下ssh密钥登录配置步骤(免密码登录)和ssh-keygen 命令常用参数
    Debian9.5下ssh密钥登录配置步骤(免密码登录)和ssh-keygen 命令常用参数
    CentOS6.9下sftp配置和scp用法
    Debian9.5下sftp配置和scp用法
    SSH概述与配置文件说明
    Linux下的权限掩码umask
    gcc 高版本兼容低版本 技巧 :指定 -specs={自定义specs文件} 参数。可以搞定oracle安装问题
    apt 之 最强技能:【欺骗】,文雅点【偷梁换柱】!
    续:纠正:ubuntu 【6.04 LTS】可以安装安装 ! for《Oracle-10.2.0.1,打补丁10.2.0.5:在 debian 版本4【不含4】以上,及 ubuntu 7.04【不含7.04】以上都可以安装!》
    ubuntu 4.10~5.10 :古老的ubuntu上安装oracle10g的情况
  • 原文地址:https://www.cnblogs.com/jdbc2nju/p/16020118.html
Copyright © 2020-2023  润新知