写点什么

【HikariCP 技术专题】原理和使用介绍(原生态开发使用)

发布于: 3 小时前
【HikariCP技术专题】原理和使用介绍(原生态开发使用)

前提简介

HikariCP 是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能,另外,和 druid 一样,HikariCP 也支持监控功能。

HikariCP 是目前最快的连接池,就连风靡一时的 BoneCP 也停止维护,主动让位给它,SpringBoot2 以后也把它设置为默认连接池。


看过 HikariCP 源码的同学就会发现,相比其他连接池,它真的非常轻巧且简单,有许多值得我们学习的地方,尤其性能提升方面,本文也就针对这一方面重点分析。

本文将包含以下内容(因为篇幅较长,可根据需要选择阅读):

  • HikariCP 的使用方法(入门案例、JNDI 使用、JMX 使用)

  • HikariCP 的配置参数详解

  • HikariCP 源码分析

使用案例

需求

使用 HikariCP 连接池获取连接对象,对用户数据进行简单的增删改查(sql 脚本项目中已提供)。

工程环境

  • JDK:1.8.0_231

  • maven:3.6.1

  • IDE:Spring Tool Suite 4.3.2.RELEASE

  • mysql-connector-java:8.0.15

  • mysql:5.7 .28

  • Hikari:2.6.1

主要步骤

通过HikariConfig加载 hikari.properties 文件,并创建HikariDataSource对象;

通过HikariDataSource对象获得Connection对象;

使用Connection对象对用户表进行增删改查。

创建项目

项目类型 Maven Project,打包方式 war(其实 jar 也可以,之所以使用 war 是为了测试 JNDI)

引入依赖

这里引入日志包,主要为了打印配置信息,不引入不会有影响的。

				<dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.12</version>            <scope>test</scope>        </dependency>        <!-- hikari -->        <dependency>            <groupId>com.zaxxer</groupId>            <artifactId>HikariCP</artifactId>            <version>2.6.1</version>        </dependency>        <!-- mysql驱动 -->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.15</version>        </dependency>        <!-- log -->        <dependency>            <groupId>org.slf4j</groupId>            <artifactId>slf4j-api</artifactId>            <version>1.7.28</version>            <type>jar</type>            <scope>compile</scope>        </dependency>        <dependency>            <groupId>ch.qos.logback</groupId>            <artifactId>logback-core</artifactId>            <version>1.2.3</version>            <type>jar</type>        </dependency>        <dependency>            <groupId>ch.qos.logback</groupId>            <artifactId>logback-classic</artifactId>            <version>1.2.3</version>            <type>jar</type>        </dependency>
复制代码
编写 hikari.properties

配置文件路径在resources目录下,因为是入门例子,这里仅给出数据库连接参数和连接池基本参数,后面会对所有配置参数进行详细说明。另外,数据库 sql 脚本也在该目录下。

#-------------基本属性--------------------------------jdbcUrl=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=trueusername=rootpassword=root#JDBC驱动使用的Driver实现类类名#默认为空,会根据jdbcUrl来解析driverClassName=com.mysql.cj.jdbc.Driver#-------------连接池大小相关参数--------------------------------#最大连接池数量#默认为10。可通过JMX动态修改maximumPoolSize=10#最小空闲连接数量#默认与maximumPoolSize一致。可通过JMX动态修改minimumIdle=0
复制代码
获取连接池和获取连接

项目中编写了JDBCUtil来初始化连接池、获取连接、管理事务和释放资源等,具体参见项目源码。路径:cn.zzs.hikari

HikariConfig config = new HikariConfig("/hikari.properties");DataSource dataSource = new HikariDataSource(config);
复制代码

编写测试

这里以保存用户为例,路径在 test 目录下的cn.zzs.hikari

@Testpublic void save() {        // 创建sql        String sql = "insert into demo_user values(null,?,?,?,?,?)";        Connection connection = null;        PreparedStatement statement = null;        try {            // 获得连接            connection = JDBCUtil.getConnection();            // 开启事务设置非自动提交            JDBCUtil.startTransaction();            // 获得Statement对象            statement = connection.prepareStatement(sql);            // 设置参数            statement.setString(1, "zzf003");            statement.setInt(2, 18);            statement.setDate(3, new Date(System.currentTimeMillis()));            statement.setDate(4, new Date(System.currentTimeMillis()));            statement.setBoolean(5, false);            // 执行            statement.executeUpdate();            // 提交事务            JDBCUtil.commit();        } catch(Exception e) {            JDBCUtil.rollback();            log.error("保存用户失败", e);        } finally {            // 释放资源            JDBCUtil.release(connection, statement, null);        }    }
复制代码

使用例子-通过 JNDI 获取数据源

需求

本文测试使用 JNDI 获取HikariDataSource对象,选择使用tomcat 9.0.21作容器。

如果之前没有接触过 JNDI ,并不会影响下面例子的理解,其实可以理解为像 spring 的 bean 配置和获取。

编写 context.xml/tomcat 下的 context 包下的 context.xml

webapp文件创建目录META-INF,并创建context.xml文件。这里面的每个 resource 节点都是我们配置的对象,类似于 spring 的 bean 节点。其中jdbc/hikariCP-test可以看成是这个 bean 的 id。

HikariCP 提供了HikariJNDIFactory来支持 JNDI 。

注意,这里获取的数据源对象是单例的,如果希望多例,可以设置singleton="false"

<?xml version="1.0" encoding="UTF-8"?><Context>  <Resource      name="jdbc/hikariCP-test"      factory="com.zaxxer.hikari.HikariJNDIFactory"      auth="Container"      type="javax.sql.DataSource"      jdbcUrl="jdbc:mysql://localhost:3306/github_demo?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=true"      username="root"      password="root"      driverClassName="com.mysql.cj.jdbc.Driver"      maximumPoolSize="10"      minimumIdle="0"/>      minimumIdle="0"/></Context>
复制代码
编写 web.xml

web-app节点下配置资源引用,每个resource-ref指向了我们配置好的对象。

<!-- JNDI数据源 --><resource-ref>   <res-ref-name>jdbc/hikariCP-test</res-ref-name>   <res-type>javax.sql.DataSource</res-type>   <res-auth>Container</res-auth></resource-ref>
复制代码
编写 Java 代码
			  String jndiName = "java:comp/env/jdbc/druid-test";        InitialContext ic = new InitialContext();        // 获取JNDI上的ComboPooledDataSource        DataSource ds = (DataSource) ic.lookup(jndiName);        JDBCUtils.setDataSource(ds);        // 创建sql        String sql = "select * from demo_user where deleted = false";        Connection connection = null;        PreparedStatement statement = null;        ResultSet resultSet = null;        // 查询用户        try {            // 获得连接            connection = JDBCUtils.getConnection();            // 获得Statement对象            statement = connection.prepareStatement(sql);            // 执行            resultSet = statement.executeQuery();            // 遍历结果集            while(resultSet.next()) {                String name = resultSet.getString(2);                int age = resultSet.getInt(3);                System.err.println("用户名:" + name + ",年龄:" + age);            }        } catch(SQLException e) {            System.err.println("查询用户异常");        } finally {            // 释放资源            JDBCUtils.release(connection, statement, resultSet);        }
复制代码

测试结果

打包项目在tomcat9上运行,访问 http://localhost:8080/hikari-demo/testJNDI.jsp ,控制台打印如下内容:

用户名:zzs001,年龄:18用户名:zzs002,年龄:18用户名:zzs003,年龄:25用户名:zzf001,年龄:26用户名:zzf002,年龄:17用户名:zzf003,年龄:18
复制代码
通过 JMX 管理连接池
需求

开启 HikariCP 的 JMX 功能,并使用 jconsole 查看。

修改 hikari.properties

在例子一基础上增加如下配置。这要设置 registerMbeans trueJMX 功能就会开启。

#-------------JMX--------------------------------#是否允许通过JMX挂起和恢复连接池#默认为falseallowPoolSuspension=false
#是否开启JMX#默认falseregisterMbeans=true
#数据源名,一般用于JMX。#默认自动生成poolName=zzs001
复制代码
编写测试类

为了查看具体效果,这里让主线程进入睡眠,避免结束。

public static void main(String[] args) throws InterruptedException {    new HikariDataSourceTest().findAll();    Thread.sleep(60 * 60 * 1000);}
复制代码

使用 jconsole 查看

运行项目,打开 jconsole,选择我们的项目后点连接,在 MBean 选项卡可以看到我们的项目。通过 PoolConfig 可以动态修改配置(只有部分参数允许修改);通过 Pool 可以获取连接池的连接数(活跃、空闲和所有)、获取等待连接的线程数、挂起和恢复连接池、丢弃未使用连接等。


配置文件详解编写

相比其他连接池,HikariCP 的配置参数非常简单,其中有几个功能需要注意:HikariCP 强制开启借出测试和空闲测试,不开启回收测试,可选的只有泄露测试。

数据库连接参数

注意,这里在url后面拼接了多个参数用于避免乱码、时区报错问题。 补充下,如果不想加入时区的参数,可以在mysql命令窗口执行如下命令:set global time_zone='+8:00'

#-------------基本属性--------------------------------jdbcUrl=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=trueusername=rootpassword=root#JDBC驱动使用的Driver实现类类名#默认为空。会根据jdbcUrl来解析driverClassName=com.mysql.cj.jdbc.Driver
复制代码

连接池数据基本参数

这两个参数都比较常用,建议根据具体项目调整。

#-------------连接池大小相关参数--------------------------------#最大连接池数量#默认为10。可通过JMX动态修改maximumPoolSize=10
#最小空闲连接数量#默认与maximumPoolSize一致。可通过JMX动态修改minimumIdle=0
复制代码

连接检查参数

针对连接失效问题,HikariCP 强制开启借出测试和空闲测试,不开启回收测试,可选只有泄露测试。

#-------------连接检测情况--------------------------------
#用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'#如果驱动支持JDBC4,建议不设置,因为这时默认会调用Connection.isValid()方法来检测,该方式效率会更高#默认为空connectionTestQuery=select 1 from dual
#检测连接是否有效的超时时间,单位毫秒#最小允许值250 ms#默认5000 ms。可通过JMX动态修改validationTimeout=5000
#连接保持空闲而不被驱逐的最小时间。单位毫秒。#该配置只有再minimumIdle < maximumPoolSize才会生效,最小允许值为10000 ms。#默认值10000*60 = 10分钟。可通过JMX动态修改idleTimeout=600000
#连接对象允许“泄露”的最大时间。单位毫秒#最小允许值为2000 ms。#默认0,表示不开启泄露检测。可通过JMX动态修改leakDetectionThreshold=0
#连接最大存活时间。单位毫秒#最小允许值30000 ms#默认30分钟。可通过JMX动态修改maxLifetime=1800000
#获取连接时最大等待时间,单位毫秒#获取时间超过该配置,将抛出异常。最小允许值250 ms#默认30000 ms。可通过JMX动态修改connectionTimeout=300000
#在启动连接池前获取连接的超时时间,单位毫秒#>0时,会尝试获取连接。如果获取时间超过指定时长,不会开启连接池,并抛出异常#=0时,会尝试获取并验证连接。如果获取成功但验证失败则不开启池,但是如果获取失败还是会开启池#<0时,不管是否获取或校验成功都会开启池。#默认为1initializationFailTimeout=1
复制代码

事务相关参数

建议保留默认就行。

#-------------事务相关的属性--------------------------------#当连接返回池中时是否设置自动提交#默认为trueautoCommit=true
#当连接从池中取出时是否设置为只读#默认值falsereadOnly=false
#连接池创建的连接的默认的TransactionIsolation状态#可用值为下列之一:NONE,TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE#默认值为空,由驱动决定transactionIsolation=TRANSACTION_REPEATABLE_READ
#是否在事务中隔离内部查询。#autoCommit为false时才生效#默认falseisolateInternalQueries=false
复制代码

JMX 参数

建议不开启 allowPoolSuspension,对性能影响较大,后面源码分析会解释原因。

#-------------JMX--------------------------------
#是否允许通过JMX挂起和恢复连接池#默认为falseallowPoolSuspension=false
#是否开启JMX#默认falseregisterMbeans=true
#数据源名,一般用于JMX。#默认自动生成poolName=zzs001
复制代码

其他

注意,这里的 dataSourceJndiName 不是前面例子中的 jdbc/hikariCP-test,这个数据源是用来创建原生连接对象的,一般用不到。

#-------------其他--------------------------------#数据库目录#默认由驱动决定catalog=github_demo
#由JDBC驱动提供的数据源类名#不支持XA数据源。如果不设置,默认会采用DriverManager来获取连接对象#注意,如果设置了driverClassName,则不允许再设置dataSourceClassName,否则会报错#默认为空#dataSourceClassName=
#JNDI配置的数据源名#默认为空#dataSourceJndiName=
#在每个连接获取后、放入池前,需要执行的初始化语句#如果执行失败,该连接会被丢弃#默认为空#connectionInitSql=
#-------------以下参数仅支持通过IOC容器或代码配置的方式--------------------------------
#TODO#默认为空#metricRegistry
#TODO#默认为空#healthCheckRegistry
#用于Hikari包装的数据源实例#默认为空#dataSource
#用于创建线程的工厂#默认为空#threadFactory=
#用于执行定时任务的线程池#默认为空#scheduledExecutor=
复制代码


发布于: 3 小时前阅读数: 3
用户头像

🏆2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

👑【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 我们始于迷惘,终于更高水平的迷惘

评论

发布
暂无评论
【HikariCP技术专题】原理和使用介绍(原生态开发使用)