web-dev-qa-db-ja.com

Tomcat接続プールが作成する接続が多すぎてスリープモードでスタックしている

Tomcat7の接続プールとMySQLを備えたTomcat6.0.29を使用しています。アプリケーションをテストすると、プールから何も再利用されませんが、新しいプールが作成され、プールの最大アクティブサイズが設定されている場合、プールには何百ものスリープ状態の接続があるため、最終的にデータベースを使用できなくなります。 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を低い数値に使用すると、これらのスリープ状態の接続がすべて削除されると確信していますが、それでは実際の問題は解決されませんか?誰かが私が間違っていることを知っていますか?どうもありがとうございました。

22
meatyowllegs

現時点では、これをテストするための環境がありませんが、接続、ステートメント、およびResultSetを閉じる必要があると信じています各クエリの後。これらのリークのいずれかが発生した場合、接続がアイドル状態(ただし必ずしもプールに戻されるとは限りません)のままになる可能性があります。

受け取るConnectionオブジェクトは、実際にはプーリング層からの一種のプロキシである必要があります。その上でcloseを呼び出すと、その接続での「予約」が解放され、プールに戻されます。 (必ずしも、基になる実際のデータベース接続を閉じるとは限りません。)

開いたままになる可能性があるため(通常は開いたままになる)、閉じられていないステートメントまたはResultSetは、プールレイヤーによって「ビジー」であることを示すものとして解釈される可能性があります。

これを確認するために、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サーバーで、ゲームオブジェクトとSQLテーブルを操作しようとして一度に4,000のアクティブなスレッドが存在する可能性があります。)

12
BRPocock

接続プールのmaxAgeプロパティを調べます。 (設定されていないことに気づきました。)

maxAgeは

この接続を維持するためのミリ秒単位の時間。接続がプールに返されると、プールは、now --time-when-connected> maxAgeに到達したかどうかを確認し、到達した場合は、接続をプールに戻すのではなく閉じます。デフォルト値は0です。これは、接続が開いたままになり、接続をプールに戻したときに経過時間チェックが実行されないことを意味します。 [ソース]

基本的に、これによりスリープ状態のスレッドを回復でき、問題を解決できるはずです。

2
PseudoNinja

おそらく、dbcp接続プールのドキュメントからのこのメモが答えかもしれません:

注:負荷の高いシステムでmaxIdleの設定が低すぎると、接続が閉じられ、すぐに新しい接続が開かれる可能性があります。これは、アクティブなスレッドが接続を開くよりも速く接続を一時的に閉じるため、アイドル状態の接続の数がmaxIdleを超えた結果です。負荷の高いシステムのmaxIdleの最適な値はさまざまですが、デフォルトが適切な開始点です。

おそらくmaxIdleは、システムのmaxActive + minIdleである必要があります。

1
slushi

これは、リソースを強制終了せずにアプリケーションをリロードしたことが原因です。そして、アプリケーションコンテキストリソースはまだ生きています。 / Catalina/localhost/.xmlを削除して元に戻すか、:: service Tomcat7 restartでサービスをより頻繁に再起動しない限り、これを解決する方法はありません。

注::コードに問題はなく、構成にも問題はありません。

応援して〜

0
Kah Win Lee

Hibernateを使用していて、一部のメソッドに@Transactionalで注釈を付けることができなかったため、この問題が発生しました。接続がプールに戻されることはありませんでした。

0
th3morg

接続プロバイダーを試して、呼び出しごとに検索するのではなく、静的として宣言されたデータソースプロバイダーを含むクラスを作成する必要があります。 InitialContextについても同じです。毎回新しいインスタンスを作成するためかもしれません。

0
TecHunter