Quarkus Reactive负载测试时数据库连接未及时释放,抛出‘Session is currently connecting to database’等错误求助
Quarkus Reactive应用k6负载测试时数据库连接无法及时释放,出现Session超时/关闭错误
问题描述
我在对Quarkus Reactive应用进行k6负载测试时遇到了问题,看起来数据库连接无法及时释放。该应用的端点逻辑为:根据接收到的code查询数据库实体,然后存储一条与该实体关联的新事件。核心代码如下:
@GET @PermitAll @Path("s/") @Produces(MediaType.TEXT_PLAIN) public Uni<Response> request(@PathParam("code") String code) { return myService.fetchEntityByCode(code) .onItem().invoke(entity-> { //persist the events here persist(SomeEvent.builder().entity(entity).build()); }) .onItem().transform(entity-> { Response.ResponseBuilder builder = new ResponseBuilderImpl(); //do some other things to build the response return builder.build(); }); } private void persist(MyEvent myEvent) { Panache.withTransaction(myEvent::persist) .onFailure().invoke(throwable -> log.error("Persisting failed", throwable)) .subscribe().with(p -> { //just a dummy subscriber to get "fire&forget" behaviour }); }
运行k6负载测试后,我收到了以下错误信息,推测这与连接未及时释放导致后续无法创建新数据库连接有关:
2022-01-30 17:04:04,865 DEBUG [org.hib.res.jdb.int.LogicalConnectionManagedImpl] (vert.x-eventloop-thread-6) `hibernate.connection.provider_disables_autocommit` was enabled. This setting should only be enabled when you are certain that the Connections given to Hibernate by the ConnectionProvider have auto-commit disabled. Enabling this setting when the Connections do not have auto-commit disabled will lead to Hibernate executing SQL operations outside of any JDBC/SQL transaction. 2022-01-30 17:04:05,052 ERROR [com.lau.lin.MyResource] (vert.x-eventloop-thread-11) Persisting failed 2022-01-30 17:04:05,052 ERROR [io.qua.mut.run.MutinyInfrastructure] (vert.x-eventloop-thread-11) Mutiny had to drop the following exception: io.smallrye.mutiny.CompositeException: Multiple exceptions caught: [Exception 0] io.vertx.core.impl.NoStackTraceThrowable: Timeout [Exception 1] java.lang.IllegalStateException: HR000060: Session is closed ...(省略部分栈追踪) Suppressed: java.lang.IllegalStateException: HR000060: Session is closed ...(省略部分栈追踪) Caused by: io.vertx.core.impl.NoStackTraceThrowable: Timeout 以及: 2022-01-30 16:29:53,581 ERROR [com.lau.lin.MyService] (vert.x-eventloop-thread-10) Persisting failed: io.smallrye.mutiny.CompositeException: Multiple exceptions caught: [Exception 0] java.lang.IllegalStateException: HR000061: Session is currently connecting to database [Exception 1] java.lang.IllegalStateException: HR000061: Session is currently connecting to database ...(省略部分栈追踪)
请问我遗漏了什么配置或代码逻辑?
回答
问题根源
你的核心问题在于异步持久化操作的生命周期脱离了请求上下文:
- 在
persist方法中,你使用subscribe().with()实现"fire&forget",这会让事务操作在后台独立运行,Quarkus的Reactive连接管理器无法追踪这些连接的使用情况,导致连接无法被及时回收。 - 同时,你在请求链中用
onItem().invoke()调用persist,invoke是同步执行的,不会等待异步的持久化操作完成,请求返回后,相关的上下文资源(包括数据库连接)可能被提前回收,进而引发Session关闭、连接超时等错误。 - 高负载下,大量未被回收的连接会耗尽连接池,导致后续请求无法获取新连接,出现你看到的超时和Session异常。
解决方案
你需要把持久化操作的Uni纳入到请求的响应链中,让Quarkus能够正确管理事务和连接的生命周期。具体修改如下:
1. 修改persist方法,返回Uni<Void>
去掉subscribe().with(),让方法返回事务操作的Uni,这样可以将其纳入请求链:
private Uni<Void> persist(MyEvent myEvent) { return Panache.withTransaction(myEvent::persist) .onFailure().invoke(throwable -> log.error("Persisting failed", throwable)); }
2. 修改请求链,使用call替代invoke
call操作符会等待传入的Uni执行完成后再继续链的流程,确保持久化操作在请求响应完成前完成,连接能被正确回收:
@GET @PermitAll @Path("s/{code}") // 注意:原Path "s/"无法正确匹配@PathParam("code"),这里修正为带参数的路径 @Produces(MediaType.TEXT_PLAIN) public Uni<Response> request(@PathParam("code") String code) { return myService.fetchEntityByCode(code) .call(entity -> persist(SomeEvent.builder().entity(entity).build())) // 用call执行异步持久化,等待完成 .onItem().transform(entity-> { // 用Response.ok()替代手动new ResponseBuilderImpl(),更简洁符合规范 Response.ResponseBuilder builder = Response.ok(); //do some other things to build the response return builder.build(); }); }
3. 优化连接池配置(可选但推荐)
检查你的application.properties,确保Reactive数据库连接池的配置符合负载需求:
# 调整连接池最大大小,根据你的负载情况设置合理值 quarkus.datasource.reactive.max-size=20 # 设置连接空闲超时,回收闲置连接 quarkus.datasource.reactive.idle-timeout=30s # Hibernate Reactive连接池大小和数据源保持一致 quarkus.hibernate-orm.reactive.connection-pool.max-size=20
为什么这样修改有效
- 将持久化操作的
Uni纳入请求链后,Quarkus可以追踪整个异步流程的生命周期,确保事务完成后及时释放数据库连接。 call操作符保证了持久化操作在响应返回前完成,避免了上下文资源被提前回收的问题。- 去掉"fire&forget"的订阅方式,消除了连接泄漏的根源,高负载下连接池不会被耗尽。
内容的提问来源于stack exchange,提问作者Marian Klühspies
相关产品推荐
相关产品推荐

