配置参数
时间校准
HikariCP 在很大程度上依赖于精确的高分辨率的定时器来提高性能和可靠性,所以使用数据库连接池 HikariCP 的应用服务器最好能够与时间源做同步,比如 NTP 服务器,否则由于 HikariCP 源码对于时间的处理可能会导致一些问题。
不稳定的时间影响的不仅仅是数据库连接池,任何定时等待、并发集合的定时轮询、带有超时的 Object.wait()
、Thread.sleep()
调用等,以及任何 Java 中需要测量时间的功能都会受到影响。
必要配置
dataSourceClassName
dataSourceClassName 和 jdbcUrl 是两种数据源的配置方式。
HikariCP 更加建议使用 dataSourceClassName,当然,两者都可以接受。需要注意的是,如果是 Spring Boot 自动装配的用户,需要使用 jdbcUrl 的基于配置的方式;当前已知的 MySQL DataSource 并不支持网络超时,建议改用 jdbcUrl 的方式。
jdbcUrl
表示 HikariCP 使用传统的、基于驱动管理器 DriverManager 的配置。虽然 HikariCP 作者认为两种配置方式中,基于 dataSourceClassName 的配置由于各种原因而更优越,但对于许多部署而言,几乎没有显著差异。
将此属性与“旧”驱动程序一起使用时,可能还需要设置 driverClassName 属性,但首先尝试不使用该属性。如果使用此属性,用户仍可以使用 DataSource 属性来配置驱动程序,实际上建议使用 URL 本身中指定的驱动程序参数。
username & password
username 和 password 分别表示从基础驱动程序获取 Connections 时使用的默认身份验证用户名和密码。
HikariCP 会将 username 属性和 password 属性分别配置在 Properties 文件中,从而传递给驱动 Driver 的 DriverManager.getConnection(jdbcUrl, props) 调用。
非必要配置
autoCommit
此属性控制从池返回的连接的默认自动提交行为。它是一个布尔值。默认值:true。
connectionTimeout
此属性控制客户端(即用户的程序)等待池中连接的最长毫秒数。如果在没有连接可用的情况下超过此时间,则将抛出 SQLException 异常。最低可接受的连接超时为250毫秒。默认值:30000(30秒)。这是一个很重要的问题排查指标。
idleTimeout
此属性控制连接允许被闲置在池中的最大时间。此设置仅适用于 minimumIdle 定义为比 maximumPoolSize 小的时候。一旦池到达 minimumIdle 连接的时候,空闲连接将不会退役。连接是否空闲而退役的最大变化为 +30 秒,平均变化为 +15 秒。在此超时之前,连接永远不会因空闲状态而退役。值为 0 意味着空闲连接永远不会从池中删除即永不超时。minimum 允许的最小值为 10000 毫秒(10秒)。默认值:600000(10分钟)。
许多防火墙和负载均衡器(通常位于应用程序和数据库之间)会占用套接字生存周期。通常,达到 idleTimeout 时,无论当前流量如何,都会切断连接。
HikariCP 设计之初就不支持空闲连接检测 test-while-idle,这是因为数据库管理员 DBA 往往会默认设置数据库最长连接时间是 60 秒,test-while-idle 会对数据库产生不必要的查询,这样就有可能导致数据库空闲连接出现超时的问题。
旧版本中,maxLifetime 由管家线程 HouseKeeper 强制执行,每 30 秒执行一次,因为 wait_timeout 减去 30 秒是推荐的 maxLifetime。但是最新版本的 HikariCP 对每个连接 connection 进行专用计时器任务,提供了几十毫秒(在高负载下长达几秒)的时间间隔,此时 maxLifetime 被安全地设置为 wait_timeout 减去5秒。如果连接退出,后台线程会执行添加操作,创建新的连接大约是 5 毫秒。如果 maxLifetime 是60秒,那么 idleTimeout 可以被设置为 0,显得就并不那么重要。
maxLifetime
此属性控制池中连接的最大生命周期。使用中的连接永远不会退役,除非它被关闭然后移除。在逐个连接的基础上,应用轻微的负衰减可以避免池中的大量消亡(在源码解析部分会深入分析)。HikariCP 作者强烈建议用户设置此值,并且它应比任何数据库或基础设施实施的连接时间限制短几秒。值为 0 表示没有最大生存期(无限生存期),当然这取决于 idleTimeout 设置。默认值:1800000(30分钟)。
30 分钟的默认超时是非常合理的,很多开发人员都会发现,在应用程序与很多数据库之间,会有高可用代理、负载均衡、防火墙等,通常这些组件会自动且独立地终止连接 30 分钟左右。
一般来说,可以将 maxLifetime 设置缩短到 900000 毫秒(15分钟)然而更好的方法是,确定 MySQL 配置的 wait_timeout 值是什么,并将 HikariCP 设置为比 maxLifetime 短几分钟。
connectionTestQuery
如果您的驱动程序支持 JDBC4,我们强烈建议不要设置此属性。这适用于不支持 JDBC4 的 Connection.isValid() 的“遗留”驱动程序 API。这是一个检测查询,在数据库连接池给出连接之前进行查询,以验证与数据库的连接是否仍然存在且有效。
如果你追求极致性能的话,建议不要配置该属性,因为不配置的时候会通过 ping 命令进行连接检测,性能会更高。
minimumIdle
此属性控制 HikariCP 尝试在池中维护的最小空闲连接数。若空闲连接低于此值且池中的总连接数小于 maximumPoolSize,则 HikariCP 将尽最大努力快速有效地添加其他连接。
然而,为了最大限度地提高性能和对峰值需求的响应能力,HikariCP 作者建议不要设置此值,而是允许 HikariCP 充当一个固定大小的连接池(如果 minimumIdle 未设置则默认为是 maximumPoolSize,因此即使 idleTimeout 设置为 1 分钟,一旦连接关闭,它将在池中被替换)。
如果设置这个值,那么 HikariCP 就会是一个大小可变的池,通过 minimumIdle 进行调解控制,即使使用情况上下浮动,HikariCP 也会保持 minimumIdle 连接可用。默认值:与 maximumPoolSize 相同。
minimumIdle 应始终小于或等于 maximumPoolSize。如果 minimumIdle 设置为更高的值,它将推高 maximumPoolSize 到相等的值。minimumIdle 逻辑上不能超过 maximumPoolSize,因为 maximumPoolSize 指定了后端数据库的实际连接的最大数量。
如果有比 minimumIdle 的数目更多的连接数,如果一个连接退役了,它不会被自动替换。但是如果数据库连接池被配置为固定大小的,或者连接关闭后如果空闲连接小于 minimumIdle 的数量,那么就会立即自动替换连接。
启用 HikariCP 的 metrics 采集,在可视化界面上可直观地显示连接的直方图,便于用户研究并确定正确的、合理的 minimumIdle 及 idleTimeout 等值。数据库连接池的调优最好基于经验数据,具体问题具体分析。
maximumPoolSize
此属性控制允许数据库连接池到达的最大大小,包括空闲和正在使用的连接。基本上,此值将确定到数据库后端的实际连接的最大数量。合理值最好由用户的执行环境决定。当池达到此大小且没有空闲连接可用时,对 getConnection() 的调用将阻塞到超时前 connectionTimeout 毫秒。关于连接池大小 PoolSize 的相关知识请关注本章后续内容。默认值:10。
metricRegistry
此属性仅通过编程配置或 IoC 容器可用。此属性允许用户指定池使用的 Codahale /Dropwizard 实例 MetricRegistry,来记录各种度量标准。如果用户需要使用 Prometheus 等监控的话,还需要做一些操作。
healthCheckRegistry
此属性仅通过编程配置或 IoC 容器可用。此属性允许用户指定池使用的 Codahale /Dropwizard 实例 HealthCheckRegistry,来报告当前系统的健康信息。默认值:无。
poolName
此属性表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。默认值:自动生成。
非常用配置
initializationFailTimeout
如果池无法成功初始化连接,则此属性控制池是否“快速失败”。任何正数都被认为是尝试获取初始连接的毫秒数;在此期间,应用程序线程将被阻塞。如果在超时发生之前无法获取连接,则将引发异常。initializationFailTimeout超时发生在connectionTimeout阶段之后。如果值为0, HikariCP将尝试获取并验证连接。如果获得连接但验证失败,则抛出异常,而不会启动池。但是,如果无法获得连接,则池将启动,但稍后获取连接的尝试会失败。小于0的值将绕过任何初始连接尝试,并且池将在尝试在后台获取连接时立即启动。因此,以后获得连接的尝试可能会失败。默认值:1。
isolatelnternalQueries
此属性决定HikariCP是否在自己的事务中隔离内部池查询,例如连接存活测试。由于这些通常是只读查询,因此很少有必要将它们封装在自己的事务中。此属性仅在autoCommit禁用时适用。默认值:false。
allowPoolSuspension
此属性控制池是否可以通过JMX挂起和恢复。这对某些故障转移自动化方案很有用。当池被挂起时,调用getConnection()将不会超时,并将一直保持到池恢复为止。默认值:false。
readOnly
此属性控制默认情况下从池中获取的Connections是否处于只读模式。请注意,某些数据库不支持只读模式的概念,而其他数据库在Connection设置为只读时提供查询优化。是否需要此属性将在很大程度上取决于那的应用程序和数据库。默认值:false。
registerMbeans
此属性控制是否注册JMX管理Bean(“MBean”)。默认值:false。
catalog
此属性为支持catalog的数据库设置默认catalog。如果未指定此属性,则使用JDBC驱动程序定义的默认catalog。默认值:driver default。
connectionlnitSql
此属性设置一个SQL语句,该语句将在每次创建新连接之后执行,然后再将该连接添加到池中。如果此SQL无效或抛出异常,它将被视为连接失败,并将遵循标准重试逻辑。默认值:无。
driverClassName
HikariCP将尝试仅基于jdbcUrl通过DriverManager解析驱动程序,但对于某些较旧的驱动程序必须指定driverClassName。除非用户收到明显的错误消息,表明未找到驱动程序,否则可忽略此属性。默认值:无。
transactionlsolation
此属性控制从池返回的连接的默认事务隔离级别。若未指定,则用JDBC驱动程序定义的默认事务隔离级别。仅当有针对所有查询的特定隔离需求时,才使用此属性。此属性的值是Connection类的常量名,如TRANSACTION_READ_COMMITTED、TRANSACTION_REPEATABLE_READ等。默认值:driverdefault。
validationTimeout
此属性控制连接测试活性的最长时间。该值必须小于connectionTimeout。最低可接受的验证超时为250毫秒。默认值:5000。
leakDetectionThreshold
此属性控制连接在记录一条指示可能连接泄漏的消息之前流出池的时间。值为0表示禁用泄漏检测。启用泄漏检测的最低可接受值是2000(2秒)。默认值:0。
dataSource
此属性仅可通过编程配置或IoC容器获得。此属性允许用户直接设置DataSource要由池包装的实例,而不是让HikariCP通过反射构造它。这在一些依赖注入框架中很有用。指定此属性后,dataSourceClassName将忽略该属性和所有特定于DataSource的属性。默认值:无。
schema
该属性为支持schema概念数据库设置默认schema。如果未指定此属性,则使用JDBC驱动程序定义的默认模式。默认值:driver default。
threadFactory
此属性仅可通过编程配置或IoC容器获得。此属性允许设置java.util.concurrent. ThreadFactory将用于创建池使用的所有线程的实例。在注入线程只能通过应用程序容器提供的ThreadFactory创建的某些受限执行环境中使用它。默认值:无。
scheduledExecutor
仅可通过编程配置或IoC容器获得。允许设置java.util.concurrent.Scheduled-ExecutorService用于各种内部调度任务的实例。如果向HikariCP提供ScheduledThread-PoolExecutor实例,建议设置setRemoveOnCancelPolicy(true)。默认值:无。
容量设置
公式
拥有一个CPU内核的计算机可以同时执行数十或数百个线程,其实这只是操作系统的一个time-slicing(时间切片)的把戏。实际上,单核只能一次执行一个线程,然后由操作系统切换上下文,并且内核为另一个线程执行代码,依此类推。这是一个基本的计算法则,给定一个CPU资源,按顺序执行A和B总是比通过时间片“同时”执行A和B要快。一旦线程数量超过了CPU核心的数量,添加更多的线程速度就会变慢,而不是更快。
设计多线程是为了尽可能地利用CPU空闲等待时间(及IO、交互等),它的代价就是要增加部分CPU时间来实现线程切换。如果CPU空闲等待时间已经比线程切换更短(线程越多,切换消耗越大),那么线程切换会非常影响性能,成为系统瓶颈。
当我们查看数据库的主要瓶颈是什么时,它们可以归纳为3个基本类别:CPU、磁盘、网络。
可以在PostgreSQL基准测试中看到,TPS速率开始在大约50个连接处变平:
下面的公式也是由PostgreSQL项目提供的,它同样在很大程度上适用于数据库。实际生产中,用户测试应用程序,即模拟预期的负载,并在此起点周围尝试不同的池设置。
connections =((core_count×2)+ effective_spindle_count)
effective_spindle_count 就是磁盘列阵中的硬盘数。某PostgreSQL项目做过测试,拥有一个硬盘的小型4核i7服务器的连接池大小设置为:9 = ((4×2) + 1)。这样的连接池大小居然可以轻松处理3000个前端用户在6000 TPS下进行简单查询。这里的线程数指的是数据库进程中的线程,通常通过连接调度的查询将在数据库的单个线程上执行,连接和线程在大多数情况下几乎都是一对一的关系。
在公式的配置上,如果加大压力,TPS会下降,RT会上升,可以适当地根据情况进行调整加大。这时应考虑整体系统性能,同时考虑线程执行需要的等待时间,合理地设计线程数目。但是,不要过度配置你的数据库。如果一直给HikariCP增加压力,那么性能也可能会产生急剧的下降。
- 如果查询是5毫秒,则通过单个连接每秒可以处理大约200个查询。
- 如果查询是100毫秒,单个连接每秒可以处理大约10个查询。
- 对数据库而言,5台Server各自拥有一个连接和每台Server拥有5个连接几乎相同。
- 如果有慢查询和快速查询的混合,由于慢查询会锁定数据库连接池,建议长时间运行的查询使用单独的数据库连接池。
数据库连接池的大小设置针对的是单个组件、应用程序和单个数据库。在多个微服务同时访问单个共享数据库的时候,情况就会变得比较棘手,这时就需要提炼出一些应对它的方法。对于单个应用程序来说,如果同时运行任何超过10个查询会产生较低的吞吐量和较高的延迟,如果平均查询时间是2ms,那么在2ms的时间内可以满足10个查询。如果我们允许20个查询(HikariCP数据库连接池的大小为20),则在2ms内无法满足同时运行20个查询,并且由于调度程序的时间分片开销,实际上可能超过2倍。如果微服务A和微服务B同时对该数据库进行查询,并持续生成连续负载,按照10个查询的稳定性大小,服务A和服务B分别设置数据库连接池大小为5是有意义的。对于生成连续负载的N服务,最大池大小的公式很简单:(最大同时数据库查询)/ N。
但是服务A和服务B之间还有一些突发性,比如A或者B一段时间内爆发大量查询,一段时间内相对空闲,这时候最大数据库连接池大小设为5~10会比较好。微服务越多,这个就越复杂。度量标准Metrics是进行数据库连接池大小评估的很有效的工具。首先,根据对其需求的最佳预测,为每个微服务选择一个池大小,而不考虑其他服务,并收集每项服务的指标:Active connections(活动连接)、Idle connections(空闲连接)、Waiting threadcount(等待线程数)、Usage(每个连接离开池多长时间)。然后,根据这些指标找到峰值并尝试确定原因,进行分类对比,并确定总活动超过数据库容量和CPU的点是否与峰值对应。最后,通过不断重复这些过程,根据收集的指标慢慢进行匹配对比,观察分析,从而找到数据库连接池最合适的大小。
池锁
池锁代表数据库连接池由于由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
很多实际使用数据库连接池的用户在开发过程中或多或少遇到过池锁的问题。增大连接池大小可以缓解池锁问题,但是扩大池之前可以先检查一下应用层面是否能够调优,而不要直接调整连接池大小。
避免池锁有一个简单的资源分配公式:
pool size = Tn x (Cm -1) + 1
Tn 是线程的最大数量,Cm 是单个线程持有的同时连接的最大数量。例如,有3个线程(Tn = 3),每个线程需要4个连接来执行某个任务(Cm = 4)。确保永不死锁的池大小是: 3 x(4-1)+ 1 = 10。
如果有10个线程(Tn = 10),每个线程需要2个连接(Cm =2)来执行某个任务,确保永不出现死锁所需的池大小是:10x(2-1)+ 1 = 11。
再比如,最多有8个线程(Tn = 8),每个线程需要3个连接来执行一些任务(Cm =3)。确保永远不可能死锁的池大小是: 8×(3-1)+ 1 = 17注意,这不一定是最佳池大小,但是是避免死锁所需的最低限度,也就是最小的池大小。
在某些环境中,使用JTA(Java事务管理器)可以显著减少从同一个Connection返回getConnection()到当前事务中已经存储Connection的线程所需的连接数。
建议
混合了长时间运行事务和非常短的事务的系统通常是最难调整任何连接池的系统。在这些情况下,创建两个池实例可以很好地工作(例如,一个用于长时间运行的事务,另一个用于“实时”查询)。
对于长期运行的外部系统,例如只允许一定数量的作业同时运行的作业执行队列,这时作业队列大小就是连接池非常合适的大小。
总结如下:连接池是综合每个应用系统的业务逻辑特性,加上应用硬件配置,加上应用部署数量,再加上DB硬件配置和最大允许连接数等测试出来的,很难用一个公式简单进行计算。连接数及超时时间设置不正确经常会带来较大的性能问题,并影响整个服务能力的稳定性。具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。压测很重要。
Fixed Pool Design
基于大量实践得到的经验值:
maximumPoolSize: 20
minimumIdle: 10
HikariCP的初始版本只支持固定大小的池。作者初衷是,HikariCP是专门为具有相当恒定负载的系统而设计的,并且倾向于连接池大小保持其运行时允许达到的最大大小,所以作者认为没有必要将代码复杂化以支持动态调整大小。
如果想要支持动态调整不同负载的最佳池大小设置,可以配合Hikari使用同为the Mutual Admiration Society成员的VladMihalcea研究的FlexyPool。当然,连接池上限受到数据库最优并发查询容量的限制,这正是HikariCP关于池大小起作用的地方。然而,在池的最小值和最大值之间,FlexyPool不断尝试递增,确保该池大小在提供服务的过程中动态负载一直是正确的。
FlexyPool具有以下默认策略:
- 在超时时递增池。此策略将增加连接获取超时时的目标连接池最大大小。连接池具有最小的大小,并可根据需要增长到最大大小。该溢出是多余的连接,让连接池增长超过其初始的缓冲区最大尺寸。每当检测到连接获取超时时,如果池未增长到其最大溢出大小,则当前请求不会失败。
- 重试尝试。此策略对于那些缺少连接获取重试机制的连接池非常有用。
核心逻辑
minIdle来指定空闲连接的最小数量,maxPoolSize指定连接池连接最大值,默认初始化的时候,是初始化minIdle大小的连接,如果minIdle与maxPool-Size值相等,那就是初始化时把连接池填满。idleTimeout用来指定空闲连接的时长,maxLifetime用来指定所有连接的时长。com.zaxxer.hikari.housekeeping.periodMs用来指定连接池空闲连接处理及连接池数补充的HouseKeeper任务的调度时间间隔。所有的连接在maxLifetime之后都得重连一次,以保证连接池的活性。
MySQL 配置
MySQL 的主要性能配置参数是:
- prepStmtCacheSize:这将设置MySQL驱动程序将为每个连接缓存的预准备语句数。默认值为保守25。我们建议将其设置为250~500。
- prepStmtCacheSqlLimit:这是驱动程序将缓存的已准备SQL语句的最大长度。MySQL的默认值是256。根据我们的经验,特别是对于像Hibernate这样的ORM框架,这个默认值远低于生成的语句长度的阈值。我们推荐的设置是2048。
- cachePrepStmts:如果事实上禁用了高速缓存,则上述任何参数都不会产生任何影响,因为它是默认情况下。必须将此参数设置为true。
- useServerPrepStmts:较新版本的MySQL支持服务器端预处理语句,这可以提供显著的性能提升。应将此属性设置为true。
HikariCP 的典型 MySQL 配置如下:
jdbcUrl=jdbc:mysql://localhost:3306/simpsons
user=test
password=test
dataSource.cachePrepStmts=true
dataSource.prepStmtCacheSize=250
dataSource.prepStmtCacheSqlLimit=2048
dataSource.useServerPrepStmts=true
dataSource.useLocalSessionState=true
dataSource.rewriteBatchedStatements=true
dataSource.cacheResultSetMetadata=true
dataSource.cacheServerConfiguration=true
dataSource.elideSetAutoCommits=true
dataSource.maintainTimeStats=false
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.