前言:httpclient是我们日常开发不和绕过的一个话题,开发中遇到很多疑问,看过很多blog,这里通过阅读关键部分的源码,总结了部分个人觉得比较重要的部分。
具体使用强烈建议阅读《亿级流量网站架构核心技术》这本书的第10.3,12.2节,写得非常好。

一. 重要参数含义

  1. setConnectTimeout:跟目标服务建立连接超时时间。
  2. setSocketTimeout:socket连接超时时间。
  3. setConnectionRequestTimeout:请求等待超时时间。
  4. keepAliveStrategy:一个http连接存活时间,通常根据http头的Keep-Alive参数来决定,如果Keep-Alive没有定义则默认-1,即无限期的保活这个http连接。如果服务设置了将多久不活动的连接丢掉以释放系统资源的话,服务器丢掉这个连接时是不会通知client端的。
    如果有必要,可以自定义连接在pool中的保活时长:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ConnectionKeepAliveStrategy keepAliveStrategy = new DefaultConnectionKeepAliveStrategy() {
    @Override
    public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
    long keepAlive = super.getKeepAliveDuration(response, context);
    if (keepAlive == -1) {
    //保活5s
    keepAlive = 5000;
    }
    return keepAlive;
    }
    };
  5. connTimeToLive,连接在pool中存活时长,默认-1,即永不失效。

  6. evictExpiredConnectionsevictIdleConnections,过期和空闲连接剔除策略,如果启用其一,则会独立起一个线程每隔固定时间(默认5s)去检查一遍所有连接,将过期和空闲连接剔除。
    IdleConnectionEvictor中实现线程定时检测,具体逻辑见:connectionManager.closeExpiredConnections()connectionManager.closeIdleConnections()

注:HttpClient的所有各种策略,参数,逻辑,我们都可以在HttpClientBuilder.build()中找到解释!


二. 连接池

实例化一个HttpClient时,如果我们没有手动指定PoolingHttpClientConnectionManager对象,则会创建一个默认的PoolingHttpClientConnectionManager对象。
默认PoolingHttpClientConnectionManager,默认创建5per route(域名链路),10个链接connection
Tip:per route表示路由个数,即能够同时请求的域名个数,默认情况,我们对每隔域名的请求只会开两个端口,即两条tcp链路,去请求。这样我们手动开启100个线程去同时请求同一个接口时,其实并发只是2,有98个线程会处于阻塞状态。
Tip:通常如果我们需要频繁请求某个接口时,可以将PoolingHttpClientConnectionManager作为全局变量保存,以支持route和connection的重用
Tip:当同时需要进行多次http请求时,我们可以在方法内手动new PoolingHttpClientConnectionManager并指定setMaxTotal()setDefaultMaxPerRoute(),并启用多线程去请求。

(1) PoolingHttpClientConnectionManager使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PoolingHttpClientHelper {
private static final Logger logger = LoggerFactory.getLogger(PoolingHttpClientHelper.class);

PoolingHttpClientConnectionManager poolingConnManager;

public <T> T get(String path, Class<T> clazz) {
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(poolingConnManager)
.build();
//实例化GET请求
HttpGet httpget = new HttpGet(path);
//JSON数据格式
String json = null;
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpget);
InputStream in = response.getEntity().getContent();
json = IOUtils.toString(in, Charset.defaultCharset());
in.close();
} catch (UnsupportedOperationException | IOException e) {
logger.error("http get request exception.path:{}", path, e);
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
logger.error("http get request release exception.path:{}", path, e);
}
}
}
return JSON.parseObject(json, clazz);
}
}

注意:这里要in.close();response.close();都释放,以备下次重用。
注意:这里没有做线程安全考虑,代码仅做参考!

(2)BasicClientConnectionManager

BasicClientConnectionManager是一个简单的连接管理类,内部仅维护一个connection,即每次只允许一个http请求,虽然它线程安全,多线程下还是串行执行,如果临近两次请求的是同一个route,则这个connection会被重用,否则先关闭然后再新建一个connection。


三. 重试机制

  1. 请求异常重试
    HttpClient默认对http请求IOException类型异常重试3次,通过DefaultHttpRequestRetryHandler类实现(是一个单例模式类),重试之间没有时间间隔,如果请求过程中抛以下异常:InterruptedIOException,UnknownHostException,ConnectException,SSLException则不进行重试,这些不启动异常重试的类可以自定义,初始化时保存在Set<Class<? extends IOException>> nonRetriableClasses中。
    Tip:重试对象的单例模式:public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
    Tip:可以通过disableAutomaticRetries()方法关闭请求异常重试。
    Tip:可以通过setRetryHandler()方法自定义重试逻辑。
  2. 服务不可用重试
    如果服务器返回503(服务器过载或维护等),则进行Service Unavailable Retry,默认重试一次,每次间隔1s,通过ServiceUnavailableRetryStrategy类实现,ServiceUnavailableRetryExec对其进行decorator,以便在请求完成之后调用,确定是否进行重试。
    Tip:setServiceUnavailableRetryStrategy()方法可以自定义服务器过载重试逻辑!
    Tip:只有在服务器返回503才会进行重试,HttpClient默认启用重试,重试一次,间隔一秒!

注:当实际中,我们依赖外部HTTP接口时,建议对接口做相关重试和降级机制,如请求失败,我们每隔1s,30s,1min去再次请求服务器;若仍不行,我们可以报警,并降级,直接返回预定的内容!


四. 调优

上述几点基本也是向着调优策略进行的,下面列出几点通用调优:

  1. 连接数,通过HttpClientBuilder#maxConnTotal#maxConnPerRoute分别设置最大连接数和单route连接数,增加吞吐能力。
  2. 获取连接的超时时间,通过RequestConfig#connectionRequestTimeout进行设置。调小获取连接超时时间能够有效提高响应速度并且降低积压请求量,但相应的也会增加请求失败的几率。
  3. 建立连接和route响应的超时时间,调小能够有效的降低bad request对连接的占用,留给质量更好的请求,有效提高系统提高吞吐能力及响应速度。否则有可能在峰值期被慢请求占满连接池,导致系统瘫痪。两者分别可通过RequestConfig#connectionTimeoutsocketTimeout进行设置。
  4. 开启BackoffStrategyExec,对状况差的route进行降级处理,将连接让给其他route。

参考

  1. 《亿级流量网站架构核心技术》第10.3,12.2节
  2. http://hc.apache.org/httpcomponents-client-4.3.x/quickstart.html