• 排查JAVA程序100次请求后连接被断开问题
  • 发布于 2个月前
  • 219 热度
    0 评论

一个搞c的好友反馈,他请求java服务端接口,连续调用100次后连接就被断开了,他想让服务端调整一下,连接保持keepalive即可,不要设置连接次数。已抓包排查,确实是服务端主动关闭连接。服务端使用springboot项目内置tomcta9,服务器端声称bean中已进行相关设置keepalive不限制请求数。


01问题复现
有关100次请求后连接被断开,之前也没注意过相关配置,未遇到过相关问题,但考虑到100这个值,基本可以断定有相关属性配置。排查此类问题的最好方式就是自己复现一下,使用springboot2快速复现下。
server:
  port: 9000
  tomcat:
    max-keep-alive-requests: 3
细看了下ServerProperties类,确实有一个相关属性:maxKeepAliveRequests。源代码如图:

注释写得清楚明白,就不解释了,直接开测。这里设置为3,1个连接接收3个请求就断开连接。本文使用httpclient进行测试,向服务端发起9次请求。比较简单,直接贴一下测试结果如图:

wireshar抓包数据如图:

02问题排查
经过问题复现,显然max-keep-alive-requests配置是生效的,并且该配置的默认值就是100,结合问题描述(服务端在bean中设置相关属性),看来服务端是自己实例化了tomcat的相关bean。从max-keep-alive-requests向上排查,找到了ServerProperties.Tomcat,继续向上排查找到了TomcatWebServerFactoryCustomizer,找到了该属性设置相关代码:
private void customizeMaxKeepAliveRequests(ConfigurableTomcatWebServerFactory factory, int maxKeepAliveRequests) {
    // 堆代码 duidaima.com
    factory.addConnectorCustomizers((connector) -> {
        ProtocolHandler handler = connector.getProtocolHandler();
        if (handler instanceof AbstractHttp11Protocol) {
            AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) handler;
            protocol.setMaxKeepAliveRequests(maxKeepAliveRequests);
        }
    });
}
该属性是经由factory方法添加的,继续向上排查,找到了TomcatServletWebServerFactory,继续向上排查看下factory是哪里造出来的,找到了ServletWebServerFactoryConfiguration,相关代码:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {

    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
            ObjectProvider<TomcatContextCustomizer> contextCustomizers,
            ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.getTomcatConnectorCustomizers()
                .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatContextCustomizers()
                .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatProtocolHandlerCustomizers()
                .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
        return factory;
    }

}
所以咱也如法炮制一下,自己实例化一个TomcatServletWebServerFactory,并进行相关属性设置:
@Configuration
public class StarTomcatConfig {

    @Bean
    public TomcatServletWebServerFactory createTomcatServletWebServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(connector -> {
            Http11NioProtocol protocolHandler = (Http11NioProtocol) connector.getProtocolHandler();
            protocolHandler.setMaxKeepAliveRequests(5);
            protocolHandler.setPort(9005);
        });
        return factory;
    }
}
将配置文件中max-keep-alive-requests属性移除,重新测试,发现客户端调用端口100次后才会改变,端口的变更正是新连接的表象,基本可以说明代码里的配置(protocolHandler.setMaxKeepAliveRequests(5);)没有生效。代码不生效就不生效吧,于是反馈给好友,修改配置文件中max-keep-alive-requests: -1即可,好友反馈ok了。

03继续探索
好友咨询代码中设置了为什么没有生效?我说应该是被覆盖了。想想也不太对,为什么要覆盖用户自己设置的属性而使用默认值呢,决定继续探索下。思路也比较简单,在上面代码贴图中第9行打个断点,调用这行代码的地方就是属性覆盖的地方,结果如图:

可以看到我们添加的配置(addConnectorCustomizers)都会进入到这里this.tomcatConnectorCustomizers,且排在首位,所以经过for循环后,系统的默认值或者是在server.tomcat下的配置属性值会将我们的customizers结果覆盖,所以导致代码中的配置失效。当然我们也可以想其他办法使代码里的配置生效,但是得不偿失,就在配置文件中配置就好了。
用户评论