Tomcat 连接池创建太多连接,卡在睡眠模式

Posted

技术标签:

【中文标题】Tomcat 连接池创建太多连接,卡在睡眠模式【英文标题】:Tomcat Connection pool creating too many connections, stuck in sleep mode 【发布时间】:2011-08-13 17:05:18 【问题描述】:

我使用的是 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 和 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&amp;useUnicode=true&amp;characterEncoding=utf8"/>

<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource>
</Context>

我确定我可以将 removeAbandonedTimeout 设置为较低的值,它会清除所有这些休眠连接,但这并不能解决真正的问题,不是吗?有谁知道我做错了什么?非常感谢。

【问题讨论】:

try/catch 中的代码是否抛出异常?目前,您只是忽略和可能的输出。 这只是从 apache 的 tomcat 连接池文档中复制粘贴的。尽管发生了这个问题,但我的应用程序具有正常的 try catch 语句并没有抛出任何异常。 这种情况发生在您的开发环境中还是在您进行热部署和/或重新部署战争的任何地方? 你解决过这个问题吗?我们在 Oracle 中看到了同样的情况,连接池设置在服务器的 context.xml 中。 你应该在你的应用程序中只创建 one InitialContext 来重新测试它,(并且可能只有一个 envCtx.lookup()) ,(即只做 2 和/或此代码中的前 3 行 once ,不是针对每个请求) 【参考方案1】:

我目前还没有测试环境,但是,我相信您应该在每次查询后关闭连接、语句和结果集;如果其中任何一个泄漏,它可能会使 Connection 处于空闲(但不一定返回到池)状态。

您收到的 Connection 对象实际上应该是池层的一种代理;在其上调用close 会释放您对该连接的“保留”并将其返回到池中。 (它不一定会关闭底层的实际数据库连接。)

因为它可能保持打开状态(通常会保持打开状态),池层可能会将未关闭的语句或结果集解释为仍然“忙碌”的指示。

您可以检查(例如,调试器使这很容易)Connection 对象以在运行时识别其状态,以确认这一点。

为简单起见 (...),我们在每次数据库连接调用后的 finally 块中使用了以下讨厌的小例程:… finally closeAll (rs, st, con); ,以确保它们会立即脱离上下文。

    /**
 * Close a bunch of things carefully, ignoring exceptions. The
 * “things” supported, thus far, are:
 * <ul>
 * <li>JDBC ResultSet</li>
 * <li>JDBC Statement</li>
 * <li>JDBC Connection</li>
 * <li>Lock:s</li>
 * </ul>
 * <p>
 * This is mostly meant for “finally” clauses.
 *
 * @param things A set of SQL statements, result sets, and database
 *            connections
 */
public static void closeAll (final Object... things) 
    for (final Object thing : things) 
        if (null != thing) 
            try 
                if (thing instanceof ResultSet) 
                    try 
                        ((ResultSet) thing).close ();
                     catch (final SQLException e) 
                        /* No Op */
                    
                
                if (thing instanceof Statement) 
                    try 
                        ((Statement) thing).close ();
                     catch (final SQLException e) 
                        /* No Op */
                    
                
                if (thing instanceof Connection) 
                    try 
                        ((Connection) thing).close ();
                     catch (final SQLException e) 
                        /* No Op */
                    
                
                if (thing instanceof Lock) 
                    try 
                        ((Lock) thing).unlock ();
                     catch (final IllegalMonitorStateException e) 
                        /* No Op */
                    
                
             catch (final RuntimeException e) 
                /* No Op */
            
        
    

这只是语法糖,以确保没有人忘记输入更长、更丑陋的 if (null != con) try con.close () catch (SQLException e) 节(通常对 ResultSet、Statement 和 Connection 重复 3 次);并消除了我们的格式化程序将在触及数据库的每个代码块上变成全屏附带清理代码的“视觉噪音”。

(那里的Lock 支持是针对潜在异常的一些相关但令人讨厌的死锁状态,这与数据库根本没有太大关系,但我们以类似的方式使用以减少行一些线程同步代码中的噪音。这是来自一个 MMO 服务器,它一次可能有 4,000 个活动线程,试图操纵游戏对象和 SQL 表。)

【讨论】:

+1 你必须关闭连接才能将其释放到连接池【参考方案2】:

查看连接池的 maxAge 属性。 (我注意到你没有设置它。)

最大年龄是

保持此连接的时间(以毫秒为单位)。当连接 返回到池中,池将检查是否现在 - time-when-connected > maxAge 已达到,如果是,则关闭 连接而不是将其返回到池中。默认值 为 0,这意味着连接将保持打开状态并且没有年龄 将连接返回到池时进行检查。 [source]

基本上这可以让你的睡眠线程被恢复并且应该可以解决你的问题。

【讨论】:

【参考方案3】:

也许来自 dbcp 连接池文档的注释可能是答案:

注意:如果在负载较重的系统上将 maxIdle 设置得太低,您可能会看到连接被关闭并且几乎立即新连接被打开。这是由于活动线程暂时关闭连接的速度比打开连接的速度快,导致空闲连接的数量超过 maxIdle。对于重负载系统,maxIdle 的最佳值会有所不同,但默认值是一个很好的起点。

也许 maxIdle 应该 == maxActive + minIdle 适合您的系统。

【讨论】:

【参考方案4】:

您应该尝试使用连接提供程序,创建一个包含声明为静态的数据源提供程序的类,而不是在每次调用时都查找它。与您的 InitialContext 相同。可能是因为你每次都创建一个新实例。

【讨论】:

【参考方案5】:

关于您的代码的简短说明:不仅是 Connection,ResultSet 和 Statement 也应该在 finally 块中关闭。 BRPocock 给出的方法应该可以正常工作。

但这不是每个请求 10 个连接的实际原因!每个请求获得 10 个连接的原因是因为您将 minIdle 设置为 10,这意味着您在创建每个 DataSource 时强制它具有 10 个连接。 (尝试将 minIdle 设置为 5,您会看到每个请求将有 5 个连接。)

您的问题是,每次发出请求时,都会创建一个新的数据源:

DataSource datasource = (DataSource) envCtx.lookup("jdbc/dbname");

我不确定查找究竟是如何工作的,但是鉴于您来自 mysql 的进程列表,我非常确信对于每个请求,您都会创建一个新的数据源。如果你有一个 Java Servlet,那么你应该在你的主 Servlet 的 init() 方法中创建 DataSource。然后您可以从那里获得连接。

在我的情况下,我做了其他事情,因为我有多个数据源(多个数据库),我使用以下代码来获取我的数据源:

private DataSource getDataSource(String db, String user, String pass)

    for(Map.Entry<String, DataSource> entry : datasources.entrySet())
    
        DataSource ds = entry.getValue();
        if(db.equals(ds.getPoolProperties().getUrl()))
        
            return ds;
        
    
    System.out.println("NEW DATASOURCE CREATED ON REQUEST: " + db);

    DataSource ds = new DataSource(initPoolProperties(db, user, pass));
    datasources.put(db, ds);
    return ds;

数据源依赖一个 equals 方法,虽然速度不是很快,但它确实有效。我只保留一个包含我的数据源的全局 HashMap,如果我请求一个尚不存在的数据源,我会创建一个新的。我知道这很好用,因为在日志中我只看到每个数据库一次的 NEW DATASOURCE CREATED ON REQUEST: dbname 消息,甚至多个客户端使用相同的数据源。

【讨论】:

【参考方案6】:

我遇到了这个问题,因为我使用的是 Hibernate,并且未能使用 @Transactional 注释我的一些方法。连接从未返回到池中。

【讨论】:

【参考方案7】:

发生这种情况是由于您的应用程序在没有资源终止的情况下重新加载。而且您的应用程序上下文资源仍然存在。除非您删除 /Catalina/localhost/.xml 并将其放回原处或更频繁地使用 :: service tomcat7 restart

进行服务重启,否则无法解决此问题>

注意:: 你的代码没有错,你的配置没有错..

加油~

【讨论】:

以上是关于Tomcat 连接池创建太多连接,卡在睡眠模式的主要内容,如果未能解决你的问题,请参考以下文章

Tomcat 池不重置池。获得太多连接错误

为什么我的应用程序会打开太多睡眠连接?

如何记录 Tomcat 7 JDBC 连接池、连接创建

我可以将锁定模式设置为在 Informix - JDBC - tomcat 连接池中等待吗?

如何在tomcat连接中定义模式属性

Tomcat JDBC连接池(Tomcat 9)