SO_TIMEOUT
防止socket read hang住CONNCT_TIMEOUT
防止connect超时很长,默认采用系统3或7次SYNC重试 windows:21s linux:128sREQUEST_CONNECTION_TIMEOUT
防止从连接池获取不到连接时hang住defaultMaxPerRoute
, 默认是2,每个route(可以认为是一个域名,但是看它的equals方法,本地IP Address不同也不是一个route)只能建立两个HTTP连接(已经验证)ConnectionReuseStrategy
可以在这里选择关闭连接;当Response未被Consume时(根本上是EofSensorInputstream.close()
),直接关闭response是能完成连接关闭的instantOf HttpEntityEnclosingRequest
Github-HC-4.5.14去除自动重试HttpClientBuilder.create().disableAutomaticRetries().build();
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:158)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
释放连接池中的连接 是用 CloseableHttpResponse.close()
还是HttpRequestBase.releaseConnection()
同时测试时EntityUtils
也有释放链接的作用。具体怎么释放不是很明白
同样看MainClientExec 中在捕获到IOException时也是会终止连接,而 NoHttpResponse是IOException的一种.
// HttpResponseProxy
@Override
public void close() throws IOException {
if (this.connHolder != null) {
this.connHolder.close();
}
}
// ConnectionHolder
@Override
public void close() throws IOException {
releaseConnection(false);
}
我好奇这个reusable一直为false,难道是不再用了吗,那么连接池的意义何在? 真的是close了,默认没有复用
private void releaseConnection(final boolean reusable) {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) {
if (reusable) {
this.manager.releaseConnection(this.managedConn,
this.state, this.validDuration, this.tunit);
} else {
try {
this.managedConn.close();
log.debug("Connection discarded");
} catch (final IOException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
} finally {
this.manager.releaseConnection(
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
/// ...
推测与实际一致,链接被close
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnDdSXrq-1684054299659)(en-resource://database/11356:1)]
// HttpRequestBase
/**
* A convenience method to simplify migration from HttpClient 3.1 API. This method is
* equivalent to {@link #reset()}.
*
* @since 4.2
*/
public void releaseConnection() {
reset();
}
// AbstractExecutionAwareRequest 根据调试这里的Cancellable是ConnectionHolder
public void reset() {
final Cancellable cancellable = this.cancellableRef.getAndSet(null);
if (cancellable != null) {
cancellable.cancel();
}
this.aborted.set(false);
}
ConnectionHolder
的cancel
就很特殊了,是终止链接
// ConnectionHolder
@Override
public boolean cancel() {
final boolean alreadyReleased = this.released.get();
log.debug("Cancelling request execution");
abortConnection();
return !alreadyReleased;
}
ConnectionHolder
的所有操作
// ConnectionHolder
@Override
public void releaseConnection() {
releaseConnection(this.reusable);
}
// 如果连接已经释放,abort没有任何起作用
@Override
public void abortConnection() {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) {
try {
this.managedConn.shutdown();
log.debug("Connection discarded");
} catch (final IOException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
} finally {
this.manager.releaseConnection(
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
}
}
}
}
@Override
public boolean cancel() {
final boolean alreadyReleased = this.released.get();
log.debug("Cancelling request execution");
abortConnection();
return !alreadyReleased;
}
@Override
public void close() throws IOException {
releaseConnection(false);
}
对应日志MainClientExec开头都是在ConnectionHolder类中输出,它这个log类很奇怪
2022/08/31 17:19:52:382 CST [DEBUG] MainClientExec - Cancelling request execution
2022/08/31 17:19:52:382 CST [DEBUG] DefaultManagedHttpClientConnection - http-outgoing-0: Shutdown connection
2022/08/31 17:19:52:383 CST [DEBUG] MainClientExec - Connection discarded 丢弃连接
toString的时候的确释放了连接,并且是采用了最好的方式:
ConnectionHolder#releaseConnection
的 reuse
为True
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEQwj9WD-1684054299661)(en-resource://database/11358:1)]
org.apache.http.impl.execchain.ResponseEntityProxy#streamClosed
关闭了流;注意不要被简单的InputStream
迷惑了,Wrap了好几层呢;它有个eofwatcher
是ResponseEntityProxy
, proxy中eofDetected
处理releaseConnection了, 而releaseConnection
是考虑了Keepalive机制的
实际上不只toString,EntityUtils中各种消费InputStream的方法最后都有close的动作,即使EntityUtils.consumeQuietly()
@Override
public boolean streamClosed(final InputStream wrapped) throws IOException {
try {
final boolean open = connHolder != null && !connHolder.isReleased();
// this assumes that closing the stream will
// consume the remainder of the response body:
try {
wrapped.close();
releaseConnection();
} catch (final SocketException ex) {
if (open) {
throw ex;
}
}
} catch (final IOException ex) {
abortConnection();
throw ex;
} catch (final RuntimeException ex) {
abortConnection();
throw ex;
} finally {
cleanup();
}
return false;
}
看到这里的cleanup吓我一跳,因为它又去调用了connHolder#close! ,但实际上因为链接已经被释放,并没有进行connection#close操作:
if (this.released.compareAndSet(false, true))
// ResponseEntityProxy
private void cleanup() throws IOException {
if (this.connHolder != null) {
this.connHolder.close();
}
}
private void abortConnection() throws IOException {
if (this.connHolder != null) {
this.connHolder.abortConnection();
}
}
public void releaseConnection() throws IOException {
if (this.connHolder != null) {
this.connHolder.releaseConnection(); // connectionHolder的reusable为true
}
}
ConnectionHolder
的reuse
是根据keepAlive
修改的,具体可以参考org.apache.http.impl.DefaultConnectionReuseStrategy
在MainClientExec中关注markReusable
和markNonReusable
的使用,它决定了连接是否重用。
:https://blog.51cto.com/u_15310381/3201932
如果request首部中包含Connection:Close,不复用
如果response中Content-Length长度设置不正确(小于0),不复用
如果response首部包含Connection:Close,不复用
如果reponse首部包含Connection:Keep-Alive,复用
都没命中的情况下,如果HTTP版本高于1.0则复用
注意Spring返回400时,Connection是Close
我使用的4.5.13中,看到几个Strategy
ServiceUnavailableRetryStrategy
(默认未启用)
ConnectionReuseStrategy
在builder构建中,你可以指定各种Strategy,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xMesbEpI-1684054299662)(en-resource://database/12849:1)]
实际上通过责任链模式给这个链ClientExecChain
增加相应的处理器 在覆盖编写自己的Strategy要注意builder中策略的判断顺序、override是与上个exec的执行顺序(文章最初的堆栈)
那为什么MainClientExec中有retry的for循环逻辑,最上面堆栈显示也有个RetryExec
?不是有点乱?MainClientExec中主要是为了认证需要进行重试,不是异常重试。
,关闭warapped流即可.response.close()
或者 httpPost.close()
是不对的
httpPost.close
是不对的,因为你在response.close
前必须得getContent
而后从流中读取然后关闭,在关闭流的时候,因为不是一个简单的InputStream
,而是EofSensorInputStream
,在close的时候已经释放了连接,所以在response.close
的时候不会再connHolder.releaseConnection(false)
了
测试程序功能可能不全面干扰了测试结果:比如我在测试重试时,使用了GET方法,看到了重试,但实际业务并不发的是POST;比如我在GET时在HttpContext中拿到了connection,经测试无法关闭连接,我就认为此时是无法关闭的,这其实是不准确的,实际上因为测试返回的body为空,同时MainClientExec返回的connectHolder是null;所以测试时要和实际使用一致同时结合源码看。
重写自己的ConnectionReuseStrategy
,恰当的时候return false
在未consume response
时,调用response
的close
操作也是可以关闭连接的
在IO异常时,MainClientExec是会自动关闭连接的;在抛出一个NohttpResponseException
时,连接也是会关闭的;根本原因是读到了EOF,这也是一个IO异常。
但是
conn.releaseConnection
并不会关闭连接,可能因为Keep-Alive,重用连接,将释放连接返回连池,具体可以看ConnectionReleaseTrigger的注释
我们经常会有需求修改开源代码的某个流程,嵌入我们的处理逻辑,一般人们并不能完全熟悉开源代码,不知道应该写在哪里。但是我们知道,写开源代码的大佬们技术肯定是过关并且代码经过千锤百炼,一定有这么个钩子。我最近的需要就是根据httpStatusCode去关闭ApacheHTTPClient的连接(因为istio-proxy对不可达IP返回503且没有Connection: close头),最终是搜索到了(上一节中)
那么一般怎么找到这种hook点?
6. 描述需求进行搜索
7. 翻看源码的技巧 翻看源码一个个看、弄懂逻辑是很花时间,看类名就行,比如包GitHub-HC-4.5.14下的各种Strategy:DefaultServiceUnavailableRetryStrategy.java
DefaultHttpRequestRetryHandler.java