[英]Tomcat Connection pool creating too many connections, stuck in sleep mode
我正在使用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&useUnicode=true&characterEncoding=utf8"/>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource>
</Context>
我確信我可以將removeAbandonedTimeout用於較低的數字,它會清除所有這些睡眠連接,但這不會解決真正的問題嗎? 有誰知道我做錯了什么? 非常感謝你。
我目前沒有可以測試此環境的環境,但是,我相信您應該在每次查詢后關閉Connection,Statement和ResultSet; 如果這些泄漏中的任何一個泄漏,它可能會使Connection處於空閑狀態(但不一定返回到池中)狀態。
您收到的Connection對象實際上應該是池化層的一種代理; 在它上面調用close
會釋放你在該連接上的“預留”並將其返回到池中。 (它不一定會關閉底層的,實際的數據庫連接。)
因為它可以保持打開(通常是),所以池層可以解釋未閉合的語句或結果集,作為仍然“忙”的指示。
您可以檢查(例如調試器使這很容易)Connection對象在運行時識別其狀態,以確認這一點。
為簡單起見(...),我們在每次數據庫連接調用后在finally
塊中使用了以下令人討厭的小例程: … finally { closeAll (rs, st, con); }
… 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重復三次) ,聲明和連接); 並刪除了我們的格式化程序將在觸及數據庫的每個代碼塊上變成附帶清除代碼全屏的“視覺噪音”。
(那里的Lock
支持是針對潛在異常的一些相關但令人討厭的死鎖狀態,根本沒有與數據庫有太多關系,但我們以類似的方式使用以減少某些線程中的線路噪聲 -同步代碼。這是來自一個MMO服務器,一次可能有4,000個活動線程試圖操作游戲對象和SQL表。)
查看連接池的maxAge屬性。 (我注意到你沒有設置它。)
maxAge是
保持此連接的時間(以毫秒為單位)。 當連接返回到池時,池將檢查是否已達到now-time-connected-connected> maxAge,如果是,則關閉連接而不是將其返回池。 默認值為0,這意味着連接將保持打開狀態,並且在將連接返回到池時不會進行年齡檢查。 [資源]
基本上這可以讓你的睡眠線程恢復,並解決你的問題。
關於代碼的簡短說明:不僅是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
每個數據庫只有一次NEW DATASOURCE CREATED ON REQUEST: dbname
消息,甚至多個客戶端使用相同的數據源。
或許來自dbcp連接池文檔的這個注釋可能是答案:
注意:如果在負載很重的系統上將maxIdle設置得太低,您可能會看到連接被關閉,幾乎立即打開新連接。 這是因為活動線程暫時關閉連接的速度比打開它們的速度快,導致空閑連接的數量超過maxIdle。 對於負載較重的系統,maxIdle的最佳值會有所不同,但默認值是一個很好的起點。
也許maxIdle應該== maxActive + minIdle為您的系統。
我遇到了這個問題,因為我使用的是Hibernate並且未能使用@Transactional
注釋我的一些方法。 連接永遠不會返回池中。
這個問題是由於您的應用程序重新加載而沒有資源殺戮。 您的應用程序上下文資源仍然存在。 現在要解決這個問題,除非你刪除/Catalina/localhost/.xml並將其放回或更頻繁地使用:: service tomcat7 restart重啟服務
注意::您的代碼沒有任何問題,您的配置沒有任何問題..
歡呼〜
您應該嘗試使用連接提供程序,創建一個類,該類將包含聲明為static的數據源提供程序,而不是每次調用時查找它。 您的InitialContext也是如此。 也許是因為你每次都創建一個新實例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.