Tomcat 连接池创建过多连接,卡在睡眠模式
我使用的是 Tomcat 6.0.29,带有 Tomcat 7 的连接池和 MySQL。测试我的应用程序,它不会重用池中的任何内容,但最终会创建一个新池,最终我无法使用数据库,因为设置池的最大活动大小时,池中有数百个休眠连接到 20。
请参阅此处作为参考:
+----+------+-----------------+--------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------------+--------+---------+------+-------+------------------+
| 2 | root | localhost:51877 | dbname | Sleep | 9 | | NULL |
| 4 | root | localhost | NULL | Query | 0 | NULL | show processlist |
| 5 | root | localhost:49213 | dbname | Sleep | 21 | | NULL |
| 6 | root | localhost:53492 | dbname | Sleep | 21 | | NULL |
| 7 | root | localhost:46012 | dbname | Sleep | 21 | | NULL |
| 8 | root | localhost:34964 | dbname | Sleep | 21 | | NULL |
| 9 | root | localhost:52728 | dbname | Sleep | 21 | | NULL |
| 10 | root | localhost:43782 | dbname | Sleep | 21 | | NULL |
| 11 | root | localhost:38468 | dbname | Sleep | 21 | | NULL |
| 12 | root | localhost:48021 | dbname | Sleep | 21 | | NULL |
| 13 | root | localhost:54854 | dbname | Sleep | 21 | | NULL |
| 14 | root | localhost:41520 | dbname | Sleep | 21 | | NULL |
| 15 | root | localhost:38112 | dbname | Sleep | 13 | | NULL |
| 16 | root | localhost:39168 | dbname | Sleep | 13 | | NULL |
| 17 | root | localhost:40427 | dbname | Sleep | 13 | | NULL |
| 18 | root | localhost:58179 | dbname | Sleep | 13 | | NULL |
| 19 | root | localhost:40957 | dbname | Sleep | 13 | | NULL |
| 20 | root | localhost:45567 | dbname | Sleep | 13 | | NULL |
| 21 | root | localhost:48314 | dbname | Sleep | 13 | | NULL |
| 22 | root | localhost:34546 | dbname | Sleep | 13 | | NULL |
| 23 | root | localhost:44928 | dbname | Sleep | 13 | | NULL |
| 24 | root | localhost:57320 | dbname | Sleep | 13 | | NULL |
| 25 | root | localhost:54643 | dbname | Sleep | 29 | | NULL |
| 26 | root | localhost:49809 | dbname | Sleep | 29 | | NULL |
| 27 | root | localhost:60993 | dbname | Sleep | 29 | | NULL |
| 28 | root | localhost:36676 | dbname | Sleep | 29 | | NULL |
| 29 | root | localhost:53574 | dbname | Sleep | 29 | | NULL |
| 30 | root | localhost:45402 | dbname | Sleep | 29 | | NULL |
| 31 | root | localhost:37632 | dbname | Sleep | 29 | | NULL |
| 32 | root | localhost:56561 | dbname | Sleep | 29 | | NULL |
| 33 | root | localhost:34261 | dbname | Sleep | 29 | | NULL |
| 34 | root | localhost:55221 | dbname | Sleep | 29 | | NULL |
| 35 | root | localhost:39613 | dbname | Sleep | 15 | | NULL |
| 36 | root | localhost:52908 | dbname | Sleep | 15 | | NULL |
| 37 | root | localhost:56401 | dbname | Sleep | 15 | | NULL |
| 38 | root | localhost:44446 | dbname | Sleep | 15 | | NULL |
| 39 | root | localhost:57567 | dbname | Sleep | 15 | | NULL |
| 40 | root | localhost:56445 | dbname | Sleep | 15 | | NULL |
| 41 | root | localhost:39616 | dbname | Sleep | 15 | | NULL |
| 42 | root | localhost:49197 | dbname | Sleep | 15 | | NULL |
| 43 | root | localhost:59916 | dbname | Sleep | 15 | | NULL |
| 44 | root | localhost:37165 | dbname | Sleep | 15 | | NULL |
| 45 | root | localhost:45649 | dbname | Sleep | 1 | | NULL |
| 46 | root | localhost:55397 | dbname | Sleep | 1 | | NULL |
| 47 | root | localhost:34322 | dbname | Sleep | 1 | | NULL |
| 48 | root | localhost:54387 | dbname | Sleep | 1 | | NULL |
| 49 | root | localhost:55147 | dbname | Sleep | 1 | | NULL |
| 50 | root | localhost:47280 | dbname | Sleep | 1 | | NULL |
| 51 | root | localhost:56856 | dbname | Sleep | 1 | | NULL |
| 52 | root | localhost:58369 | dbname | Sleep | 1 | | NULL |
| 53 | root | localhost:33712 | dbname | Sleep | 1 | | NULL |
| 54 | root | localhost:44315 | dbname | Sleep | 1 | | NULL |
| 55 | root | localhost:54649 | dbname | Sleep | 14 | | NULL |
| 56 | root | localhost:41202 | dbname | Sleep | 14 | | NULL |
| 57 | root | localhost:59393 | dbname | Sleep | 14 | | NULL |
| 58 | root | localhost:38304 | dbname | Sleep | 14 | | NULL |
| 59 | root | localhost:34548 | dbname | Sleep | 14 | | NULL |
| 60 | root | localhost:49567 | dbname | Sleep | 14 | | NULL |
| 61 | root | localhost:48077 | dbname | Sleep | 14 | | NULL |
| 62 | root | localhost:48586 | dbname | Sleep | 14 | | NULL |
| 63 | root | localhost:45308 | dbname | Sleep | 14 | | NULL |
| 64 | root | localhost:43169 | dbname | Sleep | 14 | | NULL |
它为每个请求恰好创建 10 个,即 minIdle 和 minIdle 。 InitialSize 属性如下所示。
下面是嵌入到 jsp 页面中的示例测试代码。该代码不是我的应用程序中的代码,只是用于查看问题是否出在我的代码中,但问题仍然存在。
Context envCtx;
envCtx = (Context) new InitialContext().lookup("java:comp/env");
DataSource datasource = (DataSource) envCtx.lookup("jdbc/dbname");
Connection con = null;
try {
con = datasource.getConnection();
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("select * from UserAccount");
int cnt = 1;
while (rs.next()) {
out.println((cnt++)+". Token:" +rs.getString("UserToken")+
" FirstName:"+rs.getString("FirstName")+" LastName:"+rs.getString("LastName"));
}
rs.close();
st.close();
} finally {
if (con!=null) try {con.close();}catch (Exception ignore) {}
}
这是我的 context.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/dbname"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
testWhileIdle="true"
testOnBorrow="true"
testOnReturn="false"
validationQuery="SELECT 1"
validationInterval="30000"
timeBetweenEvictionRunsMillis="30000"
maxActive="20"
minIdle="10"
maxWait="10000"
initialSize="10"
removeAbandonedTimeout="60"
removeAbandoned="true"
logAbandoned="true"
minEvictableIdleTimeMillis="30000"
jmxEnabled="true"
jdbcInterceptors=
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
username=""
password=""
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/dbname?autoReconnect=true&useUnicode=true&characterEncoding=utf8"/>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource>
</Context>
我确信我可以使用removeAbandonedTimeout 到一个较低的数字,它会清除所有这些睡眠连接,但这并不能解决真正的问题,不是吗?有谁知道我做错了什么?非常感谢。
I'm using Tomcat 6.0.29, with Tomcat 7's connection pool and MySQL. Testing my application, it doesn't reuse anything from the pool, but ends up creating a new pool, to eventually where I cannot use the database because there are hundreds of sleeping connections in the pool when the max active size for the pool is set to 20.
See here for reference:
+----+------+-----------------+--------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------------+--------+---------+------+-------+------------------+
| 2 | root | localhost:51877 | dbname | Sleep | 9 | | NULL |
| 4 | root | localhost | NULL | Query | 0 | NULL | show processlist |
| 5 | root | localhost:49213 | dbname | Sleep | 21 | | NULL |
| 6 | root | localhost:53492 | dbname | Sleep | 21 | | NULL |
| 7 | root | localhost:46012 | dbname | Sleep | 21 | | NULL |
| 8 | root | localhost:34964 | dbname | Sleep | 21 | | NULL |
| 9 | root | localhost:52728 | dbname | Sleep | 21 | | NULL |
| 10 | root | localhost:43782 | dbname | Sleep | 21 | | NULL |
| 11 | root | localhost:38468 | dbname | Sleep | 21 | | NULL |
| 12 | root | localhost:48021 | dbname | Sleep | 21 | | NULL |
| 13 | root | localhost:54854 | dbname | Sleep | 21 | | NULL |
| 14 | root | localhost:41520 | dbname | Sleep | 21 | | NULL |
| 15 | root | localhost:38112 | dbname | Sleep | 13 | | NULL |
| 16 | root | localhost:39168 | dbname | Sleep | 13 | | NULL |
| 17 | root | localhost:40427 | dbname | Sleep | 13 | | NULL |
| 18 | root | localhost:58179 | dbname | Sleep | 13 | | NULL |
| 19 | root | localhost:40957 | dbname | Sleep | 13 | | NULL |
| 20 | root | localhost:45567 | dbname | Sleep | 13 | | NULL |
| 21 | root | localhost:48314 | dbname | Sleep | 13 | | NULL |
| 22 | root | localhost:34546 | dbname | Sleep | 13 | | NULL |
| 23 | root | localhost:44928 | dbname | Sleep | 13 | | NULL |
| 24 | root | localhost:57320 | dbname | Sleep | 13 | | NULL |
| 25 | root | localhost:54643 | dbname | Sleep | 29 | | NULL |
| 26 | root | localhost:49809 | dbname | Sleep | 29 | | NULL |
| 27 | root | localhost:60993 | dbname | Sleep | 29 | | NULL |
| 28 | root | localhost:36676 | dbname | Sleep | 29 | | NULL |
| 29 | root | localhost:53574 | dbname | Sleep | 29 | | NULL |
| 30 | root | localhost:45402 | dbname | Sleep | 29 | | NULL |
| 31 | root | localhost:37632 | dbname | Sleep | 29 | | NULL |
| 32 | root | localhost:56561 | dbname | Sleep | 29 | | NULL |
| 33 | root | localhost:34261 | dbname | Sleep | 29 | | NULL |
| 34 | root | localhost:55221 | dbname | Sleep | 29 | | NULL |
| 35 | root | localhost:39613 | dbname | Sleep | 15 | | NULL |
| 36 | root | localhost:52908 | dbname | Sleep | 15 | | NULL |
| 37 | root | localhost:56401 | dbname | Sleep | 15 | | NULL |
| 38 | root | localhost:44446 | dbname | Sleep | 15 | | NULL |
| 39 | root | localhost:57567 | dbname | Sleep | 15 | | NULL |
| 40 | root | localhost:56445 | dbname | Sleep | 15 | | NULL |
| 41 | root | localhost:39616 | dbname | Sleep | 15 | | NULL |
| 42 | root | localhost:49197 | dbname | Sleep | 15 | | NULL |
| 43 | root | localhost:59916 | dbname | Sleep | 15 | | NULL |
| 44 | root | localhost:37165 | dbname | Sleep | 15 | | NULL |
| 45 | root | localhost:45649 | dbname | Sleep | 1 | | NULL |
| 46 | root | localhost:55397 | dbname | Sleep | 1 | | NULL |
| 47 | root | localhost:34322 | dbname | Sleep | 1 | | NULL |
| 48 | root | localhost:54387 | dbname | Sleep | 1 | | NULL |
| 49 | root | localhost:55147 | dbname | Sleep | 1 | | NULL |
| 50 | root | localhost:47280 | dbname | Sleep | 1 | | NULL |
| 51 | root | localhost:56856 | dbname | Sleep | 1 | | NULL |
| 52 | root | localhost:58369 | dbname | Sleep | 1 | | NULL |
| 53 | root | localhost:33712 | dbname | Sleep | 1 | | NULL |
| 54 | root | localhost:44315 | dbname | Sleep | 1 | | NULL |
| 55 | root | localhost:54649 | dbname | Sleep | 14 | | NULL |
| 56 | root | localhost:41202 | dbname | Sleep | 14 | | NULL |
| 57 | root | localhost:59393 | dbname | Sleep | 14 | | NULL |
| 58 | root | localhost:38304 | dbname | Sleep | 14 | | NULL |
| 59 | root | localhost:34548 | dbname | Sleep | 14 | | NULL |
| 60 | root | localhost:49567 | dbname | Sleep | 14 | | NULL |
| 61 | root | localhost:48077 | dbname | Sleep | 14 | | NULL |
| 62 | root | localhost:48586 | dbname | Sleep | 14 | | NULL |
| 63 | root | localhost:45308 | dbname | Sleep | 14 | | NULL |
| 64 | root | localhost:43169 | dbname | Sleep | 14 | | NULL |
It creates exactly 10 for each request, which is the minIdle & InitialSize attribute as seen below.
Here is the sample test code embedded into a jsp page. The code is not the code in my application and just used to see if the issue was with my code, but the problem still persisted.
Context envCtx;
envCtx = (Context) new InitialContext().lookup("java:comp/env");
DataSource datasource = (DataSource) envCtx.lookup("jdbc/dbname");
Connection con = null;
try {
con = datasource.getConnection();
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("select * from UserAccount");
int cnt = 1;
while (rs.next()) {
out.println((cnt++)+". Token:" +rs.getString("UserToken")+
" FirstName:"+rs.getString("FirstName")+" LastName:"+rs.getString("LastName"));
}
rs.close();
st.close();
} finally {
if (con!=null) try {con.close();}catch (Exception ignore) {}
}
Here is my context.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/dbname"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
testWhileIdle="true"
testOnBorrow="true"
testOnReturn="false"
validationQuery="SELECT 1"
validationInterval="30000"
timeBetweenEvictionRunsMillis="30000"
maxActive="20"
minIdle="10"
maxWait="10000"
initialSize="10"
removeAbandonedTimeout="60"
removeAbandoned="true"
logAbandoned="true"
minEvictableIdleTimeMillis="30000"
jmxEnabled="true"
jdbcInterceptors=
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
username=""
password=""
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/dbname?autoReconnect=true&useUnicode=true&characterEncoding=utf8"/>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource>
</Context>
I'm sure I can use removeAbandonedTimeout to a low number and it would purge all these sleeping connections, but that wouldn't fix the real problem would it? Does anyone know what I'm doing wrong? Thank you very much.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
目前,我没有环境可以对此进行测试,但是,我相信您应该在每次查询后关闭连接、语句和结果集;如果其中任何一个泄漏,它可能会使连接处于空闲状态(但不一定返回到池中)。
您收到的 Connection 对象实际上应该是池层的一种代理;对其调用
close
会释放该连接上的“保留”并将其返回到池中。 (它不一定会关闭底层的实际数据库连接。)因为它可能保持打开状态(通常会保持打开状态),所以未关闭的语句或结果集可能会被池层解释为仍然“忙”的指示。
您也许能够检查(例如调试器使这变得容易)Connection 对象以识别其在运行时的状态,以确认这一点。
为了简单起见(…),我们在每次数据库连接调用之后的
finally
块中使用了以下令人讨厌的小例程:…finally { closeAll (rs, st, con); }
,确保它们会立即脱离上下文。这只是语法糖,以确保没有人忘记放入更长、更难看的
if (null != con) { try { con.close () } catch (SQLException e) {} }
节(对于ResultSet、Statement、Connection通常重复3次);并消除了我们的格式化程序将在接触数据库的每个代码块上变成全屏附带清理代码的“视觉噪音”。(其中的
Lock
支持是针对一些相关的,但令人讨厌的潜在异常死锁状态,这与数据库根本没有太大关系,但我们以类似的方式使用来减少某些线程同步代码中的线路噪声来自 MMO 服务器,该服务器可能同时有 4,000 个活动线程尝试操作游戏对象和 SQL 表。)I don't have an environment to test this in, at the moment, however, I believe that you should be closing your Connection, Statement, and ResultSet after each query; if any of these leak, it could leave the Connection hanging in an idle (but not necessarily returned to the pool) state.
The Connection object you receive should actually be a sort of proxy from the pooling layer; calling
close
on it releases your "reservation" on that connection and returns it to the pool. (It will not necessarily close the underlying, actual database connection.)Because it could be remaining open (usually will be), unclosed Statements or ResultSets could be interpreted by the pool layer as an indication of being still “busy.”
You may be able to inspect (e.g. debugger makes this easy) the Connection object to identify its state at run-time, to confirm this.
For simplicity (…) we used the following nasty little routine in the
finally
blocks after every database connection call:… finally { closeAll (rs, st, con); }
, ensuring that they would fall out of context immediately.This was just syntactic sugar to ensure that nobody forgot to put in the longer, uglier stanza of
if (null != con) { try { con.close () } catch (SQLException e) {} }
(usually repeated three times for ResultSet, Statement, and Connection); and removed the "visual noise" of what our formatter would turn into a full screen of incidental cleanup code on every block of code that touched the database.(The
Lock
support in there was for some related, but nasty, deadlock states on potential exceptions, that didn't have much to do with the database at all, but we used in a similar way to reduce the line noise in some thread-synchronization code. This is from an MMO server that might have 4,000 active threads at a time trying to manipulate game objects and SQL tables.)查看连接池的 maxAge 属性。 (我注意到你没有设置它。)
maxAge 是
基本上这可以让你的睡眠线程被恢复并应该解决你的问题。
Look into the maxAge property of the Connection pool. ( I noticed you didn't have it set.)
maxAge is
Basically this allows your sleeping threads to be recovered and should solve your problem.
也许 dbcp 连接池文档中的这条注释可能就是答案:
也许您的系统的 maxIdle 应该 == maxActive + minIdle 。
perhaps this note from the dbcp connection pool docs may be the answer:
perhaps maxIdle should == maxActive + minIdle for your system.
关于代码的简短说明:不仅是 Connection,而且 ResultSet 和 Statement 也应该在 Final 块中关闭。 BRPocock 给出的方法应该可以正常工作。
但这并不是每个请求有 10 个连接的真正原因!每个请求获得 10 个连接的原因是因为您已将 minIdle 设置为 10,这意味着您在创建 DataSource 时强制每个 DataSource 有 10 个连接。 (尝试将 minIdle 设置为 5,您会发现每个请求将有 5 个连接。)
您的情况的问题是,每次执行请求时,您都会创建一个新的数据源:
我不确定如何查找完全有效,但是考虑到来自 mysql 的进程列表,我非常确信对于每个请求,您都会创建一个新的数据源。如果您有 Java Servlet,那么您应该在主 Servlet 的 init() 方法中创建数据源。从那里您可以从中获取连接。
就我而言,我做了其他事情,因为我有多个数据源(多个数据库),我使用以下代码来获取我的数据源:
数据源依赖于 equals 方法,该方法并不是很快,但它确实有效。我只保留一个包含我的数据源的全局 HashMap,如果我请求一个尚不存在的数据源,我会创建一个新的数据源。我知道这非常有效,因为在日志中我只看到每个数据库仅出现一次
NEW DATASOURCE CREATED ON REQUEST: dbname
消息,甚至多个客户端使用相同的数据源。A short note on your code: not only Connection, but the ResultSet and Statement should be closed in the Finally block as well. The method given by BRPocock should work fine.
But that is not the actual reason for your 10 connections per request! The reason you get 10 connections each request is because you have set minIdle to 10, meaning that you force each DataSource to have 10 connections when you create it. (Try to set minIdle to 5, and you see that you will have 5 connections per request.)
The problem in your case is, that every time you do a request, you create a new DataSource:
I'm not sure how the lookup exactly works, but given your processlist from mysql i'm pretty convinced that for every request you create a new datasource. If you have a Java Servlet, then you should create the DataSource in the init() method of your main Servlet. From there you can then get connections from it.
In my case I did something else, because I have multiple DataSources (multiple databases) I use the following code to get my datasource:
The datasource relies on an equals method which is not really fast, but yea it works. I just keep a global HashMap containing my datasources, and if I request a datasource that does not exist yet, I create a new one. I know this works very well because in the logs I only see the
NEW DATASOURCE CREATED ON REQUEST: dbname
message only once per database, even multiple clients use the same datasource.您应该尝试使用连接提供程序,创建一个类,其中包含声明为静态的数据源提供程序,而不是每次调用时都查找它。对于您的 InitialContext 也是如此。也许是因为您每次都创建一个新实例。
You should try with a connection provider, create a class which will contain your datasource provider declared as static instead of looking for it every call. Same for your InitialContext. Maybe it's because you create a new instance each time.
我遇到这个问题是因为我正在使用 Hibernate 并且未能使用
@Transactional
注释我的一些方法。连接从未返回到池中。I had this problem because I was using Hibernate and failed to annotate some of my methods with
@Transactional
. The connections were never returned to the pool.发生这种情况是由于您的应用程序重新加载而没有资源终止。并且您的应用程序上下文资源仍然存在。除非您删除 /Catalina/localhost/.xml 并将其放回去或更频繁地使用 :: service tomcat7 restart 重新启动服务,否则无法解决此问题
注意::你的代码没有问题,你的配置没有问题..
欢呼~
This Happen is due to your application reload without Resource Killing. And your application context resource is still alive. There is noway to solve this unless you delete the /Catalina/localhost/.xml and put it back or more frequent doing service restart with :: service tomcat7 restart
NOTE:: Nothing wrong with your code, nothing wrong with your configuration..
cheer~