Tomcat连接池创建了太多连接,陷入了睡眠模式

时间:2011-04-27 09:42:05

标签: java mysql tomcat connection-pooling

我正在使用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用于较低的数字,它会清除所有这些睡眠连接,但这不会解决真正的问题吗?有谁知道我做错了什么?非常感谢你。

7 个答案:

答案 0 :(得分:11)

我目前没有可以测试此环境的环境,但是,我相信您应该在每次查询后关闭Connection,Statement和ResultSet;如果其中任何一个泄漏,它可能会使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重复三次);并删除了我们的格式化程序会在触及数据库的每个代码块上变成附带清理代码全屏的“视觉噪音”。

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

答案 1 :(得分:2)

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

maxAge是

  

保持此连接的时间(以毫秒为单位)。连接时   回到游泳池,游泳池会检查现在是否 -   连接时间&gt;已达到maxAge,如果是,则关闭   连接而不是将其返回池中。默认值   为0表示连接将保持打开状态且没有年龄   返回连接到池后将进行检查。 [source]

基本上这可以让您的睡眠线程得以恢复并解决您的问题。

答案 2 :(得分:1)

或许来自dbcp连接池文档的这个注释可能就是答案:

  

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

对于你的系统,maxIdle应该== maxActive + minIdle。

答案 3 :(得分:1)

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

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

您的案例中的问题是,每次执行请求时,都会创建一个新的DataSource:

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

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

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

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消息一次,即使多个客户端也使用相同的数据源。

答案 4 :(得分:0)

您应该尝试使用连接提供程序,创建一个类,该类将包含声明为static的数据源提供程序,而不是每次调用时查找它。您的InitialContext也是如此。也许是因为你每次都创建一个新实例。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

此问题是由于您的应用程序重新加载而没有资源杀戮。您的应用程序上下文资源仍然存在。除非您删除 /Catalina/localhost/.xml 并将其放回或更频繁地使用:: service tomcat7 restart

注意::您的代码没有任何问题,您的配置没有任何问题..

欢呼〜