java - define - tomcat jdbc connection pool example
El grupo de conexiones de Tomcat crea demasiadas conexiones, bloqueado en modo de suspensiĆ³n (7)
Estoy usando Tomcat 6.0.29, con el grupo de conexiones de Tomcat 7 y MySQL. Al probar mi aplicación, no reutiliza nada del grupo, sino que termina creando un grupo nuevo, hasta que finalmente no puedo usar la base de datos porque hay cientos de conexiones inactivas en el grupo cuando se establece el tamaño máximo activo para el grupo. a 20
Vea aquí para referencia:
+----+------+-----------------+--------+---------+------+-------+------------------+
| 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 |
Crea exactamente 10 para cada solicitud, que es el atributo minIdle & InitialSize como se ve a continuación.
Aquí está el código de prueba de ejemplo incrustado en una página jsp. El código no es el código en mi aplicación y solo se utiliza para ver si el problema estaba relacionado con mi código, pero el problema aún persiste.
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) {}
}
Aquí está mi archivo 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>
Estoy seguro de que puedo usar removeAbandonedTimeout en un número bajo y purgaría todas estas conexiones inactivas, pero eso no solucionaría el problema real, ¿verdad? ¿Alguien sabe lo que estoy haciendo mal? Muchas gracias.
Busque en la propiedad maxAge de la agrupación de conexiones. (Noté que no lo tenías puesto).
maxAge es
Tiempo en milisegundos para mantener esta conexión. Cuando se devuelve una conexión a la agrupación, la agrupación verificará si se ha alcanzado el tiempo actual cuando se conectó> maxAge, y si es así, cierra la conexión en lugar de devolverla a la agrupación. El valor predeterminado es 0, lo que implica que las conexiones se dejarán abiertas y no se realizará ninguna verificación de antigüedad al devolver la conexión al grupo. [source]
Básicamente, esto permite recuperar sus hilos de dormir y debería resolver su problema.
Debe intentar con un proveedor de conexión, cree una clase que contendrá su proveedor de fuente de datos declarado como estático en lugar de buscarlo en cada llamada. Lo mismo para su InitialContext. Tal vez sea porque creas una nueva instancia cada vez.
Este suceso se debe a la recarga de la aplicación sin la eliminación de recursos. Y su recurso de contexto de aplicación todavía está vivo. Ahora hay que resolver esto a menos que elimine /Catalina/localhost/.xml y lo vuelva a colocar o haga más frecuente el reinicio del servicio con :: service tomcat7 restart
NOTA: No hay nada de malo en su código, nada de malo en su configuración ..
alegría ~
No tengo un entorno para probar esto, en este momento, sin embargo, creo que debería cerrar su conexión, declaración y conjunto de resultados después de cada consulta; si cualquiera de estas fugas, podría dejar a la Conexión en un estado inactivo (pero no necesariamente devuelto al grupo).
El objeto de conexión que recibe debe ser en realidad una especie de proxy de la capa de agrupación; llamando al close
libera su "reserva" en esa conexión y la devuelve al grupo. (No necesariamente cerrará la conexión de base de datos real y subyacente).
Debido a que podría permanecer abierto (generalmente lo estará), la capa de la agrupación podría interpretar las Declaraciones abiertas o los Conjuntos de resultados como una indicación de que todavía está "ocupado".
Es posible que pueda inspeccionar (p. Ej., El depurador facilita esta tarea) el objeto Connection para identificar su estado en tiempo de ejecución, para confirmarlo.
Para simplificar (...) utilizamos la siguiente rutina poco desagradable en los bloques finally
después de cada llamada de conexión de base de datos: … finally { closeAll (rs, st, con); }
… finally { closeAll (rs, st, con); }
, asegurándose de que queden fuera de contexto de inmediato.
/**
* 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 */
}
}
}
}
Esto fue solo azúcar sintáctica para asegurar que nadie se olvidó de poner la estrofa más larga y fea de if (null != con) { try { con.close () } catch (SQLException e) {} }
(generalmente se repite tres veces para ResultSet , Declaración y conexión); y eliminó el "ruido visual" de lo que nuestro formateador se convertiría en una pantalla completa de código de limpieza incidental en cada bloque de código que tocaba la base de datos.
(El soporte de Lock
allí estaba para algunos estados de interbloqueo relacionados, pero desagradables en las posibles excepciones, que no tenían mucho que ver con la base de datos, pero usamos de una manera similar para reducir el ruido de línea en algunos hilos). código de sincronización. Esto proviene de un servidor MMO que podría tener 4.000 subprocesos activos a la vez al tratar de manipular los objetos del juego y las tablas SQL).
Quizás esta nota de los documentos de la agrupación de conexiones dbcp puede ser la respuesta:
NOTA: Si maxIdle está configurado demasiado bajo en sistemas muy cargados, es posible que vea que las conexiones se cierran y casi de inmediato se abran nuevas conexiones. Esto se debe a que las hebras activas cierran momentáneamente las conexiones más rápido de lo que las abren, lo que hace que el número de conexiones inactivas aumente por encima de maxIdle. El mejor valor para maxIdle para sistemas muy cargados variará, pero el valor predeterminado es un buen punto de partida.
quizás maxIdle debería == maxActive + minIdle para su sistema.
Tuve este problema porque estaba usando Hibernate y no pude anotar algunos de mis métodos con @Transactional
. Las conexiones nunca fueron devueltas a la piscina.
Una breve nota sobre su código: no solo Connection, pero ResultSet y Statement también deben cerrarse en el bloque Finally. El método dado por BRPocock debería funcionar bien.
¡Pero esa no es la razón real de tus 10 conexiones por solicitud! La razón por la que obtiene 10 conexiones en cada solicitud es porque ha establecido minIdle en 10, lo que significa que obliga a cada DataSource a tener 10 conexiones cuando lo crea. (Intente establecer minIdle en 5, y verá que tendrá 5 conexiones por solicitud).
El problema en su caso es que cada vez que realiza una solicitud, crea un nuevo DataSource:
DataSource datasource = (DataSource) envCtx.lookup("jdbc/dbname");
No estoy seguro de cómo funciona exactamente la búsqueda, pero dado su lista de procesos de mysql, estoy bastante convencido de que para cada solicitud creará una nueva fuente de datos. Si tiene un Servlet de Java, debe crear el DataSource en el método init () de su Servlet principal. Desde allí puedes obtener conexiones desde allí.
En mi caso, hice otra cosa, porque tengo varios DataSources (varias bases de datos). Utilizo el siguiente código para obtener mi fuente de datos:
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;
}
El origen de datos se basa en un método igual que no es realmente rápido, pero sí funciona. Simplemente mantengo un HashMap global que contiene mis fuentes de datos, y si solicito una fuente de datos que aún no existe, creo una nueva. Sé que esto funciona muy bien porque en los registros solo veo el NEW DATASOURCE CREATED ON REQUEST: dbname
mensaje NEW DATASOURCE CREATED ON REQUEST: dbname
solo una vez por base de datos, incluso varios clientes usan la misma fuente de datos.