H2 - Tomcat jdbc 连接池在达到最大限制后不回收连接
H2 - Tomcat jdbc connection pool not reclaiming connections once it hits the max limit
问题陈述
我们在嵌入式模式下使用 H2 已经有一段时间了。它上面配置了一个连接池。以下是当前池配置:
h2.datasource.min-idle=10
h2.datasource.initial-size=10
h2.datasource.max-active=200
h2.datasource.max-age=600000
h2.datasource.max-wait=3000
h2.datasource.min-evictable-idle-time-millis=60000
h2.datasource.remove-abandoned=true
h2.datasource.remove-abandoned-timeout=60
h2.datasource.log-abandoned=true
h2.datasource.abandonWhenPercentageFull=100
H2 配置:
spring.h2.console.enabled=true
spring.h2.console.path=/h2
h2.datasource.url=jdbc:h2:file:~/h2/cartdb
h2.server.properties=webAllowOthers
spring.h2.console.settings.web-allow-others=true
h2.datasource.driver-class-name=org.h2.Driver
*跳过用户名和密码属性。
我们已通过记录池属性验证上述配置是否生效。
此设置的问题是我们正在观察定期(虽然间歇性)连接池耗尽,一旦连接池达到最大限制,它就会开始为某些查询抛出以下异常。
SqlExceptionHelper.logExceptions(SqlExceptionHelper.java:129) - [http-apr-8080-exec-38] Timeout: Pool empty. Unable to fetch a connection in 3 seconds, none available[size:200; busy:200; idle:0; lastwait:3000].
此后,即使在我们重新启动网络服务器(在本例中为 tomcat)之前,即使在数小时后它也无法从该状态恢复。
H2 驱动依赖:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<scope>runtime</scope>
</dependency>
查询模式和吞吐量
我们使用 h2 为每个请求加载一些数据,然后执行一些(大约 50 个)SELECT 查询,最后删除数据。这导致在 h2 上每分钟(下班时间除外)有一致的 30k-40k 调用(根据新的遗迹监控)。
每次读操作都会获取一个新的连接,并在执行后释放。
EntityManager entityManager = null;
try {
entityManager = entityManagerFactory.createEntityManager();
Query query = entityManager.createNativeQuery(sqlQuery);
query.setParameter("cartId", cartId);
List<String> resultList = query.getResultList();
return resultList;
} finally {
if(null != entityManager) { entityManager.close(); }
}
观察结果
- 应用程序重新启动后,池利用率最低,直到某一时刻池利用率突然上升并最终达到最大限制。这会在 1-2 天内发生。
- 一旦连接池达到最大连接数限制,借用的连接数将以比返回的连接数更快的速度增加,否则返回的连接数将保持非常接近。
- 与此同时,放弃的连接数也开始随着放弃日志的增加而增加。
- 有趣的是,查询响应时间在池耗尽后保持不变。所以这种排除了慢查询。
- 即使在流量最少的最奇怪的时间也会发生此问题。所以跟流量没有关系。
请指导我们解决这个问题的正确方向。
更新
最近我们在发生此类事件时在堆栈跟踪中发现了以下原因:
Caused by: org.h2.jdbc.JdbcSQLException: Database may be already in
use: null. Possible solutions: close all other connection(s); use the
server mode [90020-196]
Caused by: java.lang.IllegalStateException:The file is locked:
nio:/root/h2/cartdb.mv.db [1.4.196/7]
Caused by: java.nio.channels.OverlappingFileLockException
所以在深入研究之后,我们决定转向内存模式,因为我们不需要在应用程序的生命周期之外保留数据。因此,不应发生文件锁定,从而减少或消除此问题。
无论哪种情况都会回来更新。
自上次更新问题以来:
在观察了一段时间的性能后,我们得出结论,在文件模式(嵌入式)中使用 H2 会以某种方式定期(尽管不规则)导致文件锁定异常。
由于我们的应用程序不需要在应用程序的生命周期之后保留数据,因此我们决定转向纯内存模式。
虽然文件锁异常的奥秘还有待揭开。
问题陈述
我们在嵌入式模式下使用 H2 已经有一段时间了。它上面配置了一个连接池。以下是当前池配置:
h2.datasource.min-idle=10
h2.datasource.initial-size=10
h2.datasource.max-active=200
h2.datasource.max-age=600000
h2.datasource.max-wait=3000
h2.datasource.min-evictable-idle-time-millis=60000
h2.datasource.remove-abandoned=true
h2.datasource.remove-abandoned-timeout=60
h2.datasource.log-abandoned=true
h2.datasource.abandonWhenPercentageFull=100
H2 配置:
spring.h2.console.enabled=true
spring.h2.console.path=/h2
h2.datasource.url=jdbc:h2:file:~/h2/cartdb
h2.server.properties=webAllowOthers
spring.h2.console.settings.web-allow-others=true
h2.datasource.driver-class-name=org.h2.Driver
*跳过用户名和密码属性。
我们已通过记录池属性验证上述配置是否生效。
此设置的问题是我们正在观察定期(虽然间歇性)连接池耗尽,一旦连接池达到最大限制,它就会开始为某些查询抛出以下异常。
SqlExceptionHelper.logExceptions(SqlExceptionHelper.java:129) - [http-apr-8080-exec-38] Timeout: Pool empty. Unable to fetch a connection in 3 seconds, none available[size:200; busy:200; idle:0; lastwait:3000].
此后,即使在我们重新启动网络服务器(在本例中为 tomcat)之前,即使在数小时后它也无法从该状态恢复。
H2 驱动依赖:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<scope>runtime</scope>
</dependency>
查询模式和吞吐量
我们使用 h2 为每个请求加载一些数据,然后执行一些(大约 50 个)SELECT 查询,最后删除数据。这导致在 h2 上每分钟(下班时间除外)有一致的 30k-40k 调用(根据新的遗迹监控)。
每次读操作都会获取一个新的连接,并在执行后释放。
EntityManager entityManager = null;
try {
entityManager = entityManagerFactory.createEntityManager();
Query query = entityManager.createNativeQuery(sqlQuery);
query.setParameter("cartId", cartId);
List<String> resultList = query.getResultList();
return resultList;
} finally {
if(null != entityManager) { entityManager.close(); }
}
观察结果
- 应用程序重新启动后,池利用率最低,直到某一时刻池利用率突然上升并最终达到最大限制。这会在 1-2 天内发生。
- 一旦连接池达到最大连接数限制,借用的连接数将以比返回的连接数更快的速度增加,否则返回的连接数将保持非常接近。
- 与此同时,放弃的连接数也开始随着放弃日志的增加而增加。
- 有趣的是,查询响应时间在池耗尽后保持不变。所以这种排除了慢查询。
- 即使在流量最少的最奇怪的时间也会发生此问题。所以跟流量没有关系。
请指导我们解决这个问题的正确方向。
更新
最近我们在发生此类事件时在堆栈跟踪中发现了以下原因:
Caused by: org.h2.jdbc.JdbcSQLException: Database may be already in use: null. Possible solutions: close all other connection(s); use the server mode [90020-196]
Caused by: java.lang.IllegalStateException:The file is locked: nio:/root/h2/cartdb.mv.db [1.4.196/7]
Caused by: java.nio.channels.OverlappingFileLockException
所以在深入研究之后,我们决定转向内存模式,因为我们不需要在应用程序的生命周期之外保留数据。因此,不应发生文件锁定,从而减少或消除此问题。
无论哪种情况都会回来更新。
自上次更新问题以来:
在观察了一段时间的性能后,我们得出结论,在文件模式(嵌入式)中使用 H2 会以某种方式定期(尽管不规则)导致文件锁定异常。
由于我们的应用程序不需要在应用程序的生命周期之后保留数据,因此我们决定转向纯内存模式。
虽然文件锁异常的奥秘还有待揭开。