连接池是什么鬼东西
Posted 背井
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连接池是什么鬼东西相关的知识,希望对你有一定的参考价值。
我们来谈谈连接池。
我甚至可以这么说:
对你来说,这意味着:
你得赶紧去检查你的连接池配置。
如果依赖默认设置,你的应用可能会出现问题。它可能导致内存泄漏和程序失去响应(即使负载并不高)。
下面我将展示一些最重要的设置和我的如何配置它们的建议。
WHAT IS CONNECTION POOL? 什么是连接池?
一个需要从数据库中写入或读取数据的普通web应用程序,它会有如下行为:
打开数据库连接 //消耗N毫秒
读/写数据
关闭连接
(顺便说一下,在老旧的CGI应用程序中,这是唯一可行的方法)
这种方式在很多情况下都表现良好。你也不需要别的什么了。但对于高性能系统来说,它有一些缺点:
第一步需要消耗一些时间。可能是几十毫秒或几百毫秒(当然,这取决于具体情况)。
步骤3(关闭连接)很容易被遗漏,这会导致连接泄漏(导致内存泄漏和其他问题)。
A NEW HERO 超人登场
这就是另一种方法诞生的原因:应用程序可以预先打开一堆连接,并一直保持着它们的开启状态。这些打开的连接就组成了连接池。那么任何操作都将变成这样的:
从池子中取出一个数据库连接 // 大部分情况下这一步都是飞快的
读/写数据
将连接返还到池子中
看起来很酷。但新的力量总是伴随着新的问题。
… AND NEW PROBLEMS 新问题来了
使用连接池时,我们需要(至少)解决以下问题:
我们应该保持多少连接?
它们应该保持多久?
如果它们看起来坏掉了呢?
如果应用程序需要的连接数超过池子当前所持有的数目怎么办?
如果有人忘记将连接返还给池子怎么办?
为了应对这些问题,连接池提供了很多设置。不过它们的默认值大多是惨不忍睹的。有兴趣吗?让我展示一下。
BASIC SETTINGS 基础设置
我将考虑Java世界中最流行的两个连接池实现:
C3P0 (https://www.mchange.com/projects/c3p0/)
HikariCP (https://github.com/brettwooldridge/HikariCP)
基础的参数当然是:
min size (最小规格,任何时刻所开启的最小连接数)
initial size (初始规格,应用启动时打开的连接数)
max size (最大规格,连接池中所能保留的最大连接数)
顺便说一下,这些是唯一具有合理默认值的设置。它们分别是:
c3p0 | HikariCP | |
min size | 3 | 10 |
initial size | 3 | 10 |
max size | 15 | 10 |
我们接下来看看更多有问题的设置。
CRITICAL SETTINGS 关键设置
CHECKOUT TIMEOUT 检出超时
应用程序从池子获取连接之前可以等待多长时间。
c3p0 设置项: checkoutTimeout
HikariCP 设置项: connectionTimeout
c3p0 | HikariCP | I recommend | |
checkoutTimeout | ∞ | 30 s | 1 ms |
Both default values are just disaster. 两边的默认值都是灾难性的。
正如之前所提到的,在大多数情况下,从池子获得连接的速度非常快。除非池子中没有多余的打开的连接。这时池子需要获取一个新连接(通常用不了一秒钟)。但是,如果达到maxSize,池子将无法打开新连接,只能等待有人将其连接返还。但是,如果应用程序有连接泄漏(一个忘了返回连接的bug),池子将永远无法回收放出去的连接!
What then happens? 然后会发生什么?
在c3p0的情况下,所有线程都被冻结在以下状态:
"qtp1905485420-495 13e09-3211" #495 prio=5 os_prio=0 tid=0x00007f20e078d800 nid=0x10d7 in Object.wait() [0x00007f204bc79000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable()
- locked <0x00000000c3295ef8> (a com.mchange.v2.resourcepool.BasicResourcePool)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource()
…
at org.hibernate.jpa.internal.QueryImpl.getResultList()
at domain.funds.FundsRepository.get()
…
看起来HikariCP的默认值“30秒”要好一点。其实不然,它在高性能应用程序中并没有真正的帮助。在这30秒内,可能会有很多新的请求,而所有请求都被冻结了。显然,应用程序将很快出现内存错误(OutOfMemory)。任何等待(waiting)只是让程序在死亡前多存活了几秒。
这就是为什么我建议将 checkoutTimeout 设置为可能的最小值:1ms。不幸的是,我们不能将其设置为0,因为0意味着无休止的等待。我们越早失败,我们就越有可能给工作线程完成其工作的机会。我们可以清楚地通知用户应用程序当前已超载,他应该稍后再试。
TEST CONNECTION ON CHECKOUT 检出时连接检查
有时池子中的连接可能会断开。数据库可能主动关闭了它们,或者系统管理员动了网线。这也是池子需要监视连接有效性的原因。
最简单的设置是 c3p0 中的 “testConnectionOnCheckout”(我在HikariCP中还没有找到类似的设置,它似乎总是启用的)。
默认值:
c3p0 | HikariCP | I recommend | |
testConnectionOnCheckout | false | true? | true |
Yes, 它默认就应该是 启用的 !
否则,日志中会出现很多这样的异常:
org.hibernate.TransactionException: Unable to rollback against JDBC Connection
at o.h.r.j.i.AbstractLogicalConnectionImplementor.rollback()
at o.h.r.t.b.j.i.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.rollback(JdbcResourceLocalTransactionCoordinatorImpl.java:294)
P.S. 如果你想获得更好的性能,可以考虑在后台进行连接测试,而不是在检出时:
testConnectionOnCheckout=false
testConnectionOnCheckin=true
idleConnectionTestPeriod=10
PREFERRED TEST QUERY 偏好的检测语句
但池子应该如何测试连接呢?
问题是,它取决于数据库。
默认情况下,两类池子都通过执行:
“connection.isValid()” (在使用 JDBC4 的情况下)
“connection.getMetaData().getTables()” (在 JDBC3 的情况下)
它可能很慢,因为 “getTables()” 每次都检索有关所有表的元信息。我们推荐如下值:
“SELECT 1” (对于 mysql)
“SELECT 1 FROM DUAL” (对于 Oracle)
通过执行这个简单而快速的查询,池子可以检查连接是否仍然存活。
MAX IDLE TIME 最大闲置时间
未使用的连接可以在池中保留多长时间:
默认值是:
c3p0 | HikariCP | I recommend | |
maxIdleTimeout | ∞ | 10 minutes | 1..10 minutes |
看着会觉得没什么大不了的,但每个打开的连接:
会在数据库中占用一些资源
会阻止其他系统连接到同一个数据库(每个数据库都有最大连接数的限制)
这就是为什么关闭未使用(空闲)连接是个好主意。我建议不要将此值设置为无限大。也许短短几分钟较为合理。
MIN POOL SIZE 最小连接数
池子中至始至终应该至少有多少个连接(即使未使用)。
c3p0 设置项: minPoolSize
HikariCP 设置项: minimumIdle
默认值:
c3p0 | HikariCP | I recommend | |
maxIdleTimeout | 3 | max pool size | 0…N |
出于同样的原因,关闭未使用的连接可能是个好主意。在大多数情况下,我会将此值设置为0或1。如果某个用户意外地决定在半夜登录到您的应用程序,他只需再等待几毫秒。不会有什么影响。
MAX CONNECTION AGE 连接最大存活时间
连接可以在池中存在多长时间(无论是空闲还是使用过)
c3p0 设置项: maxConnectionAge
HikariCP 设置项: maxLifetime
默认值:
c3p0 | HikariCP | I recommend | |
maxIdleTimeout | ∞ | 30 minutes | say, 30 minutes |
以防万一,时不时地关闭连接可能是个好主意。这很可能有助于避免一些内存泄漏。
HikariCP 文档中有如下表述:
“We strongly recommend setting this value, and it should be several seconds shorter than any database or infrastructure imposed connection time limit.”
UNRETURNED CONNECTION TIMEOUT 未返还连接的超时
一个典型的问题是连接泄漏。一些错误代码从池中获取连接,但没有返还。如何发现这个问题?
幸运的是,也存在针对这类情况的设置项:
c3p0 设置项: unreturnedConnectionTimeout
HikariCP 设置项: leakDetectionThreshold
默认值:
c3p0 | HikariCP | I recommend | |
maxIdleTimeout | disabled | disabled | 5 minutes? |
如果有任何错误代码建立了连接,但在5分钟内未返还,则池子将强制返回连接并发出如下警告:
[C3P0PooledConnectionPoolManager Logging the stack trace by which the overdue resource was checked-out.
java.lang.Exception: DEBUG STACK TRACE: Overdue resource check-out stack trace.
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource()
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1885)
at domain.application.ApplicationReportSender.sendWeeklyReport(ApplicationReportSender.java:63)
它有助你定位问题代码。
CONCLUSION 总结
我概述了一些连接池设置。其实还有更多。我根据自己的经验提出了一些合理的建议。但是你的应用程序可能有不同的负载。你的用户可能有不同的行为。你也可能会觉得我的建议很愚蠢。
没关系。不必全信我,但也请:
去检查下你的连接池配置吧!
本文译自:https://www.javaadvent.com/2018/12/wtf-connection-pools.html
原文标题:WTF CONNECTION POOLS
作者的网站上有很多优秀文章,感兴趣的读者可以点 阅读原文 查看。
关注作者
欢迎关注,一起学习 还请多多转发分享
以上是关于连接池是什么鬼东西的主要内容,如果未能解决你的问题,请参考以下文章