Spring Webflux WebClient启用Keep-Alive时SSLSocket关闭警告排查咨询
Spring Webflux WebClient 长连接SSL关闭警告问题排查
问题背景
我的应用基于Spring Webflux WebClient,需要向一个无法控制的启用SSL的第三方服务器发送数据。使用的客户端初始化代码如下:
WebClient client = WebClient.create().mutate() .clientConnector(new ReactorClientHttpConnector( HttpClient.create().keepAlive(true) .secure(sslContextSpec -> sslContextSpec.sslContext(getMySSLContext())) )).build(); final var response = client.post().uri("https://...")
问题现象
在高负载场景下(向服务器发送大量请求),频繁看到如下警告堆栈:
javax.net.ssl|WARNING|79|Keep-Alive-Timer|2023-05-15 11:05:39.124 CST|SSLSocketImpl.java:595|SSLSocket duplex close failed. Debug info only. Exception details: ( "throwable" : { java.net.SocketException: Socket is closed at java.base/java.net.Socket.shutdownInput(Socket.java:1601) at java.base/sun.security.ssl.BaseSSLSocketImpl.shutdownInput(BaseSSLSocketImpl.java:217) at java.base/sun.security.ssl.SSLSocketImpl.shutdownInput(SSLSocketImpl.java:848) at java.base/sun.security.ssl.SSLSocketImpl.bruteForceCloseInput(SSLSocketImpl.java:798) at java.base/sun.security.ssl.SSLSocketImpl.duplexCloseOutput(SSLSocketImpl.java:660) at java.base/sun.security.ssl.SSLSocketImpl.close(SSLSocketImpl.java:584) at java.base/sun.net.www.http.HttpClient.closeServer(HttpClient.java:1137) at java.base/sun.net.www.http.KeepAliveCache.run(KeepAliveCache.java:260) at java.base/java.lang.Thread.run(Thread.java:833) at java.base/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:162)} ) javax.net.ssl|WARNING|C6|Keep-Alive-Timer|2023-05-15 10:08:48.053 UTC|null:-1|SSLSocket duplex close failed. Debug info only. Exception details: ( "throwable" : { java.net.SocketException: Socket is closed at java.base/java.net.Socket.shutdownInput(Unknown Source) at java.base/sun.security.ssl.BaseSSLSocketImpl.shutdownInput(Unknown Source) at java.base/sun.security.ssl.SSLSocketImpl.shutdownInput(Unknown Source) at java.base/sun.security.ssl.SSLSocketImpl.bruteForceCloseInput(Unknown Source) at java.base/sun.security.ssl.SSLSocketImpl.duplexCloseOutput(Unknown Source) at java.base/sun.security.ssl.SSLSocketImpl.close(Unknown Source) at java.base/sun.net.www.http.HttpClient.closeServer(Unknown Source) at java.base/sun.net.www.protocol.https.HttpsClient.closeServer(Unknown Source) at java.base/sun.net.www.http.KeepAliveCache.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source) at java.base/jdk.internal.misc.InnocuousThread.run(Unknown Source)} )
咨询问题
- 该警告信息具体含义是什么?
- 是第三方服务器关闭了Socket,还是客户端自身关闭的?
- 如果是客户端关闭的,为何已设置keepAlive(true)仍会关闭Socket?如何避免客户端关闭Socket?
- 如何解决该问题?还有哪些进一步调试的方法?
问题解答
1. 警告信息的具体含义
这个警告是JDK SSL底层在尝试关闭一个已经处于关闭状态的SSL Socket时触发的。从堆栈可以看到,是客户端的Keep-Alive-Timer线程在清理长连接缓存里的Socket时,发现Socket已经被关闭,执行关闭操作时抛出了这个异常。它属于调试级别的警告,不会直接导致请求失败,但说明长连接的管理出现了不一致的情况。
2. 谁关闭了Socket?
从调用链分析,有两种可能:
- 更常见的情况是第三方服务器主动关闭:服务器通常会有连接超时、最大连接数限制等策略,当连接空闲过久或者达到连接上限时,会主动断开Socket。客户端的长连接缓存还没及时感知到这个状态,后续清理线程执行关闭操作时就会触发警告。
- 客户端自身重复关闭:客户端的连接池清理机制可能先关闭了Socket,但后续的清理逻辑重复执行了关闭操作,不过这种情况相对少见。
3. 设置keepAlive(true)为何还会关闭Socket?
keepAlive(true)只是开启了TCP长连接的支持,不代表连接会永久保持:
- 客户端的Reactor Netty连接池本身有闲置超时、最大存活时间的默认配置,闲置过久的连接会被客户端主动清理。
- 第三方服务器也会有自己的连接超时策略,不管客户端是否开启长连接,服务器都可能主动断开空闲连接。
- 网络中间设备(比如负载均衡、防火墙)也可能主动断开长时间空闲的连接。
如果要避免客户端主动关闭Socket,可以调整Reactor Netty连接池的参数:
- 延长
idleTimeout(闲置超时)的时间,尽量和服务器的超时策略匹配 - 调整
maxLifeTime(连接最大存活时长),避免客户端过早回收连接
4. 解决方法与进一步调试手段
解决方法
- 调整客户端连接池参数:显式配置连接池的闲置超时和最大存活时间,示例代码如下:
HttpClient httpClient = HttpClient.create() .keepAlive(true) // 配置连接池参数 .pool(pool -> pool .idleTimeout(Duration.ofMinutes(5)) // 闲置超时,根据服务器实际超时调整 .maxLifeTime(Duration.ofHours(1)) // 连接最大存活时间 ) .secure(sslContextSpec -> sslContextSpec.sslContext(getMySSLContext())); WebClient client = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build();
- 忽略警告(业务无异常时):这个警告只是调试信息,不会影响请求执行。如果业务没有异常,可以通过日志配置关闭
javax.net.ssl的WARNING级别日志,比如在logback.xml中添加:
<logger name="javax.net.ssl" level="ERROR"/>
- 配置TCP层面的keep-alive探测:让客户端主动探测连接是否存活,提前感知服务器的断开,避免无效的连接清理:
HttpClient httpClient = HttpClient.create() .keepAlive(true) .tcpConfiguration(tcp -> tcp .option(ChannelOption.SO_KEEPALIVE, true) .option(EpollChannelOption.TCP_KEEPIDLE, 300) // 空闲300秒后开始发探测包 .option(EpollChannelOption.TCP_KEEPINTVL, 60) // 每60秒发一次探测包 .option(EpollChannelOption.TCP_KEEPCNT, 5) // 探测失败5次后断开连接 ) .secure(sslContextSpec -> sslContextSpec.sslContext(getMySSLContext()));
进一步调试手段
- 开启Reactor Netty DEBUG日志:配置
reactor.netty.http.client的日志级别为DEBUG,查看连接的创建、复用、关闭全过程,排查连接池的管理逻辑。 - 抓包分析:用Wireshark抓取SSL连接的数据包,确认是服务器发送了FIN/RST包主动断开,还是客户端主动关闭连接。
- 对齐服务器配置:如果能联系第三方服务器运维,了解他们的连接超时、最大连接数等配置,方便客户端参数调整。
- 测试不同配置:尝试调整闲置超时、最大存活时间等参数,观察警告出现的频率变化,找到最优配置。
内容的提问来源于stack exchange,提问作者PatPanda
相关产品推荐
相关产品推荐

