一.研究背景
最近实验过程中遇到了一个小问题,个人认为是值得拿出来说一下的。整体系统是分为三部分的,分为客户端、中间数据转发以及HBase服务端。整体架构图如下所示:

那么这部分主要记录 http 通信的优化过程。一开始实现的比较仓促,使用的是传统的 HttpURLConnection 来做请求。但是在后续测试过程中,前期请求耗时是很优秀的,但是后期效率越来越低。所以想到了使用 HttpClient 连接池进行连接复用。
二.相关底层技术
(1)Http 长连接 短连接
应用层的Http协议是基于传输层的TCP协议的,所以Http长连接短连接本质上就是TCP长连接短连接。
我们从 Http1.0 1.1两个版本说起。
在Http1.0中,默认使用的是短连接,俗话说的就是 C和S每进行一次HTTP请求就要建立一次TCP连接,任务结束关闭,所以如果有大量重复地址请求的话就会反复在TCP三次握手和四次挥手当中花费巨大的时间。
在1.0版本中我们需要手动设置请求响应头的 Connection属性
Connection:keep-alive
这样就实现了长连接,即保活连接。
Http1.1进行了优化,默认便使用了长连接模式,这种情况下,每次打开一个网页完成后,C和S之间用于传输HTTP数据的TCP连接并不会关闭,如果C想要再次访问S上的网页资源,会继续使用这条已经建立好的连接。但是这个保活机制并不是永久有效的,毕竟S空间资源有限,它是通过S上的服务器软件设置的。
(2)TCP中的KEEPALIVE 和 Http 的 keep-alive 的区别
TCP-keepalive:
是由系统内核实现的。具体逻辑流程如下:
如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文。
如果对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。
如果对端主机崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。
Http的keepalive:
用于设置http长连接,通过设置响应头中的Connection属性实现。
(3)连接池技术
池化技术充满了整个项目,无论是线程池还是请求连接池。它们作为创建和管理连接的缓冲池技术,目前已广泛用于诸如数据库连接等长连接的维护和管理中,能够有效减少系统的响应时间,节省服务器资源。
三.实验部分
基于PoolingHttpClientConnectionManager实现请求连接池
package service;import org.apache.http.HttpHost;import org.apache.http.HttpRequest;import org.apache.http.NoHttpResponseException;import org.apache.http.client.HttpRequestRetryHandler;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.methods.HttpRequestBase;import org.apache.http.client.protocol.HttpClientContext;import org.apache.http.config.Registry;import org.apache.http.config.RegistryBuilder;import org.apache.http.conn.routing.HttpRoute;import org.apache.http.conn.socket.ConnectionSocketFactory;import org.apache.http.conn.socket.LayeredConnectionSocketFactory;import org.apache.http.conn.socket.PlainConnectionSocketFactory;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;import javax.net.ssl.SSLException;import javax.net.ssl.SSLHandshakeException;import java.io.IOException;import java.io.InterruptedIOException;import java.net.UnknownHostException;/*** @author han56* @description 功能描述 HttpClient工具类* @create 2022/5/27 下午1:49*/public class HttpClientUtil {static final int timeOut = 10*1000;private static CloseableHttpClient httpClient = null;private final static Object lock = new Object();//配置方法private static void config(HttpRequestBase httpRequestBase){//配置请求超时设置RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeOut).setConnectTimeout(timeOut).setSocketTimeout(timeOut).build();httpRequestBase.setConfig(requestConfig);}//获取HttpClient对象public static CloseableHttpClient getHttpClient(String url){String hostName = url.split("/")[2];int port = xxxxx;if (hostName.contains(":")) {String[] arr = hostName.split(":");hostName = arr[0];port = Integer.parseInt(arr[1]);}if (httpClient == null) {synchronized (lock) {if (httpClient == null) {httpClient = createHttpClient(20000, 4000, 10000, hostName, port);}}}return httpClient;}//创建HttpClient对象public static CloseableHttpClient createHttpClient(int maxTotal,int maxPerRoute, int maxRoute, String hostname, int port){ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainsf).register("https", sslsf).build();PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);// 将最大连接数增加cm.setMaxTotal(maxTotal);// 将每个路由基础的连接增加cm.setDefaultMaxPerRoute(maxPerRoute);HttpHost httpHost = new HttpHost(hostname, port);// 将目标主机的最大连接数增加cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);// 请求重试处理HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {if (executionCount >= 5) {// 如果已经重试了5次,就放弃return false;}if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试return true;}if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常return false;}if (exception instanceof InterruptedIOException) {// 超时return false;}if (exception instanceof UnknownHostException) {// 目标服务器不可达return false;}if (exception instanceof SSLException) {// SSL握手异常return false;}HttpClientContext clientContext = HttpClientContext.adapt(context);HttpRequest request = clientContext.getRequest();// 如果请求是幂等的,就再次尝试return request == null;};return HttpClients.custom().setConnectionManager(cm).setRetryHandler(httpRequestRetryHandler).build();}}
通过控制变量法设置实验,根据博主 程序漫漫 的建议,对系统部分模块的效率的测试应保证另一个模块的速度恒定,所以将 Netty数据中台->HBase Cluster 这一部分的耗时设置了恒定不变的常量,防止影响前段 Http 请求的测试。
程序漫漫
你好我叫孙策,公众号:程序漫漫游戏服务器入门
四.结论
在24个文件并发上传状态下 每次请求携带5000条数据:

HttpClient连接池效率

HttpURLConnctiont效率
实验结果可以大概表明,HttpClient连接池模式下连接更加稳定且效率高于HttpUrlConnct。




