使用长连接能够减少建立销毁连接的消耗,三次握手、四次挥手对性能影响是很大的。一般 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 优化