Spring 启用 SSL 的引导阻塞连接池

Spring Boot Blocked Connection Pool with enabled SSL

我有一个 Spring 启动应用程序(版本 2.5.3),它使用自签名证书启用了 SSL。 一个端点用于使用 StreamingResponseBody 在客户端下载文件。

问题

问题是,当用户通过此端点请求文件时,连接池不会被清理。 在此处展示问题的工作示例: https://github.com/smotastic/blocked-connection-pool

@GetMapping("/ping")
public ResponseEntity<StreamingResponseBody> ping() {
    // service.findSomeFile(...)
    StreamingResponseBody body = new StreamingResponseBody() {
        
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // outputStream.write(someFile);
        }
    };
    return ResponseEntity.ok(body);
}

调用它并打开 Hikari 连接池的调试日志记录后,将记录以下内容。

Pool stats (total=10, active=1, idle=9, waiting=0)

如您所见,一个连接处于活动状态,并且永远不会再次释放。如果我再调用此端点 9 次,连接池已满,任何后续请求都将 运行 超时。

到目前为止我发现了什么

启用 ssl 的应用程序、对服务或存储库的调用以及 StreamingResponseBody 的 return 值的组合似乎是我的问题。 只要我关闭其中一个因素,问题就会消失,连接会按预期释放。

// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().permitAll();
    http.requiresChannel().anyRequest().requiresSecure(); // turning this off 'solves' the problem
}

在示例中,我提供了一个简单的 FooEntity 和一个 FooRepository,我只是在其中调用了 count 方法。 我想这会打开一个新事务并在此处引发错误。

// Controller.java
@Autowired
FooRepository fooRepo;

@GetMapping("/ping")
public ResponseEntity<StreamingResponseBody> ping() {
    fooRepo.count(); // removing this line 'solves' the error
    StreamingResponseBody body = new StreamingResponseBody() {
            
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
          // outStream.write(...)
        }
    };
    return ResponseEntity.ok(body);
}

在这种情况下,FooRepository 只是 Spring 中的一个简单的 CrudRepository,没有额外的功能。

// FooRepository.java
@Repository
public interface FooRepository extends CrudRepository<Foo, Long>{

}

FooEntity 是一个带有 ID 的简单实体。

// Foo.java
@Entity
public class Foo {

    @GeneratedValue
    @Id
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    
}

在这个例子中,我使用的是 h2 数据源,但我也用 postgres 尝试过。 两者都不起作用。

# application.properties
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

通过以下配置启用 ssl。

# https via self signed certificate
server.ssl.key-store-type=${SSL_KEY_STORE_TYPE:PKCS12}
# The path to the keystore containing the certificate
server.ssl.key-store=${SSL_KEY_STORE:classpath:identity.p12}
# The password used to generate the certificate
server.ssl.key-store-password=${SSL_KEY_STORE_PASSWORD:hello}
# The alias mapped to the certificate
server.ssl.key-alias=${SSL_KEY_ALIAS:mykey}
server.ssl.enabled=${SSL_ENABLED:true}

该证书是一个自签名证书,在我的 Windows 10 机器上创建。

此外,Hikari 连接池似乎不是问题所在。我还尝试了 Tomcat JDBC 连接池。池仍然被阻塞。

# application.properties
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource

尝试添加

spring.jpa.open-in-view=false

在属性中。

在视图中引入打开会话