关于 HTTP 长连接

2023年2月22日12:27:39

使用长连接能够减少建立销毁连接的消耗,三次握手、四次挥手对性能影响是很大的。一般 RPC 如 Dubbo 默认都是长连接的,HTTP 1.1 之上也可以支持长连接了,HTTP 2.0 也支持了单一长连接的多路复用。

一般 HTTP 服务前面都会挂 nginx 做负载均衡,那么长连接的设置也分为从客户端到 nginx、从 nginx 到服务端两部分。

如果使用 Java 的 apache HTTPClient 可以通过设置 ConnectionKeepAliveStrategy 和协议为 HTTP1.1 来使用长连接,对于 4.2.x 版本设置方法如下所示:

    private static final ConnectionKeepAliveStrategy KEEP_ALIVE_STRATEGY = new ConnectionKeepAliveStrategy() {
        public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
            HeaderElementIterator it = new BasicHeaderElementIterator(
                    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    try {
                    	// 如果 response header 里有 Keep-Alive:timeout 参数,则使用其值作为长连接超时时间
                        return Long.parseLong(value) * 1000;
                    } catch (NumberFormatException ignore) {
                    }
                }
            }
            return 30 * 1000; // 30s
        }
    };

            HttpParams params = new BasicHttpParams();
            // 指定使用 http1.1,1.1里默认使用长连接
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 

            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

            //创建连接池
            PoolingClientConnectionManager cm = new PoolingClientConnectionManager(registry, 1, TimeUnit.MINUTES);
            cm.setDefaultMaxPerRoute(10);
            cm.setMaxTotal(200);

            httpClient = new DefaultHttpClient(cm, params);
            // 设置超时时间
            httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 100);
            httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 100);
            httpClient.getParams().setParameter(ClientPNames.CONN_MANAGER_TIMEOUT, 100);
            httpClient.setKeepAliveStrategy(KEEP_ALIVE_STRATEGY);

在 NGINX 请求后端服务时启用长连接需要配置:

proxy_http_version 1.1;                    
proxy_set_header Connection "";
keepalive_timeout  120s 120s;
keepalive_requests 10000;

关于长连接,有两个参数需要注意:

  1. keepalive_timeout:连接不活跃多长时间后断开,如果服务端(nginx)设置的时间比客户端短的话,可能会导致客户端请求异常。前提是 HTTPClient 都 http.connection.stalecheck 参数置为 false,也就是不在请求前检查连接的有效性。
  2. keepalive_requests:用于设置一个keep-alive连接上可以服务的请求的最大数量。当最大请求数量达到时,连接被关闭。nginx 和 Tomcat 上默认都是100,对于 QPS 较高的应用来说 100 有些太小了。

以上两个参数在 nginx、服务端都有,当 QPS 比较高时就需要做适当调整以减少或避免连接都频繁断开、建立。如果服务端使用的 Tomcat 容器,可以在 server.xml 文件里修改配置。

在 Spring Boot 2.x 里修改长连接的配置方法如下所示:

@Configuration
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.addConnectorCustomizers(connector -> {
            AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) connector.getProtocolHandler();
            protocol.setMaxKeepAliveRequests(10000);
            protocol.setKeepAliveTimeout(60000);	// 长连接超时时间,单位 ms
        });
    }
}

上面提到 HTTPClient 的 http.connection.stalecheck 参数用于设置是否在发送请求之前检查连接有效性,如果每次发请求都检查对性能影响也会较大,可以去掉该参数。但是去掉之后,在连接被 NGINX 关闭后依然发请求则会导致 NoHttpResponseException 异常,需要对其进行特殊处理,进行适当的重试。

资料

  1. nginx 优化
  • 作者:albon_arith
  • 原文链接:https://arith.blog.csdn.net/article/details/100868587
    更新时间:2023年2月22日12:27:39 ,共 2641 字。