Apache HTTPClient5 - 如何防止 Connection/Stream 被拒绝
Apache HTTPClient5 - How to Prevent Connection/Stream Refused
问题陈述
上下文
- 我是一名正在测试的软件工程师 运行 对餐厅菜单项进行排序以确认它们通过 POS 成功下单
- 简而言之,这个 POST 是一个 JSON 有效负载到一个端点,然后端点验证订单 w/a POS 定义 success/fail/other
- 其中 POS,因此每秒事务数 (TPS) 可能会有所不同,但每个后端使用相同的核心处理
- 每个项目可能高达 ~22,000 个排列,大小易于管理 JSON,需要尽快处理
- 网络可能因餐厅而异,and/or 地区,一个正在测试
- 例如有些人的延迟比其他人高得多
- 因此,无论如何,HTTPClient 应该能够智能地协商相同的内容和端点
直接问题
- 我正在使用带 PoolingAsyncClientConnectionManager 的 Apache HTTP 客户端 5 来执行菜单内容的 GET,以及 POST 检查订单是否成功
- 这是开箱即用的,但有时会失去与
Stream Refused
的连接,特别是:
org.apache.hc.core5.http2.H2StreamResetException: Stream refused
- 没有单独的调整似乎适用于所有具有可变延迟的网络环境,我可以找到
- 跟踪堆栈跟踪似乎表明流已经关闭,因此需要一种方法来保持它打开或不执行已经关闭的连接
if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
}
解决问题的一些尝试
- 尝试使用搜索引擎寻找答案,但 HTTPClient5 的搜索结果很少
- 尝试使用 official documentation 但这是稀疏的
- 将每条路线的最大连接数更改为减少的数量,将不活动验证或连接时间改为实时
- 不活动检查可能会修复 POST,但会停止某些事务的 GET
- 并且对一个 region/restaurant 的调整可能对 1 个有效然后对另一个中断,w/ 只有网络作为变量
PoolingAsyncClientConnectionManagerBuilder builder = PoolingAsyncClientConnectionManagerBuilder
.create()
.setTlsStrategy(getTlsStrategy())
.setMaxConnPerRoute(12)
.setMaxConnTotal(12)
.setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
.setConnectionTimeToLive(TimeValue.ofMinutes(2))
.build();
- 切换到具有不同超时的自定义 RequestConfig
private HttpClientContext getHttpClientContext() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.of(10, TimeUnit.SECONDS))
.setResponseTimeout(Timeout.of(10, TimeUnit.SECONDS))
.build();
HttpClientContext httpContext = HttpClientContext.create();
httpContext.setRequestConfig(requestConfig);
return httpContext;
}
用于分析的初始代码段
(除了上述片段外,还有更改尝试)
- 初始化并获取响应的包装器处理
public SimpleHttpResponse getFullResponse(String url, PoolingAsyncClientConnectionManager manager, SimpleHttpRequest req) {
try (CloseableHttpAsyncClient httpclient = getHTTPClientInstance(manager)) {
httpclient.start();
CountDownLatch latch = new CountDownLatch(1);
long startTime = System.currentTimeMillis();
Future<SimpleHttpResponse> future = getHTTPResponse(url, httpclient, latch, startTime, req);
latch.await();
return future.get();
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
return new SimpleHttpResponse(999, CommonUtils.getExceptionAsMap(e).toString());
}
}
- 使用实际处理程序和探测代码
private Future<SimpleHttpResponse> getHTTPResponse(String url, CloseableHttpAsyncClient httpclient, CountDownLatch latch, long startTime, SimpleHttpRequest req) {
return httpclient.execute(req, getHttpContext(), new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse response) {
latch.countDown();
logger.info("[{}][{}ms] - {}", response.getCode(), getTotalTime(startTime), url);
}
@Override
public void failed(Exception e) {
latch.countDown();
logger.error("[{}ms] - {} - {}", getTotalTime(startTime), url, e);
}
@Override
public void cancelled() {
latch.countDown();
logger.error("[{}ms] - request cancelled for {}", getTotalTime(startTime), url);
}
});
}
直接提问
- 有没有办法配置客户端,使其可以自行处理这些差异,而无需显式修改每个端点上下文的配置?
已修复,结合以下内容以确保连接 Live/Ready
(或者至少是稳定的)
强制 HTTP 1
HttpAsyncClients.custom()
.setConnectionManager(manager)
.setRetryStrategy(getRetryStrategy())
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.setConnectionManagerShared(true);
为 POST
设置有效 Headers
- 特别是收盘价header
req.setHeader("Connection", "close, TE");
- 注意:闲置检查有帮助,但有时仍会遭到拒绝 w/o this
按类型设置不活动检查
- 设置 POSTs 以在不活动后立即验证
- 注意:两者都使用 1000 导致某些系统的掉率很高
PoolingAsyncClientConnectionManagerBuilder
.create()
.setValidateAfterInactivity(TimeValue.ofMilliseconds(0))
- 设置GET在1秒后生效
PoolingAsyncClientConnectionManagerBuilder
.create()
.setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
给定错误上下文
- 跟踪堆栈跟踪中的连接问题到 AbstractH2StreamMultiplexer
- 显示 ConnectionHandshake.GRACEFUL_SHUTDOWN 触发流拒绝
if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
}
- 对应
connState = streamMap.isEmpty() ? ConnectionHandshake.SHUTDOWN : ConnectionHandshake.GRACEFUL_SHUTDOWN;
推理
- 如果我没理解错的话:
- 连接正在 un/intentionally 关闭
- 但是,在再次执行之前没有确认它们准备就绪
- 这导致它失败,因为流不可行
- 因此修复有效,因为(看起来)
- 给定强制 HTTP1 允许管理单个上下文
- 其中 HttpVersionPolicy NEGOTIATE/FORCE_HTTP_2 在 regions/menus
范围内有更大或同等的失败
- 并确保所有连接在使用前都有效
- 而POST由于关闭header而一直关闭,这对于HTTP2
是不可用的
- 因此
- GET 以合理的周期检查有效性
每次都检查- POST,由于是强行关闭,所以在执行前是re-acquired
- 这没有为意外关闭留下空间
- 否则可能会错误地切换到 HTTP2
会接受这个,直到出现更好的答案,因为这是稳定的,但 sub-optimal。
问题陈述
上下文
- 我是一名正在测试的软件工程师 运行 对餐厅菜单项进行排序以确认它们通过 POS 成功下单
- 简而言之,这个 POST 是一个 JSON 有效负载到一个端点,然后端点验证订单 w/a POS 定义 success/fail/other
- 其中 POS,因此每秒事务数 (TPS) 可能会有所不同,但每个后端使用相同的核心处理
- 每个项目可能高达 ~22,000 个排列,大小易于管理 JSON,需要尽快处理
- 网络可能因餐厅而异,and/or 地区,一个正在测试
- 例如有些人的延迟比其他人高得多
- 因此,无论如何,HTTPClient 应该能够智能地协商相同的内容和端点
直接问题
- 我正在使用带 PoolingAsyncClientConnectionManager 的 Apache HTTP 客户端 5 来执行菜单内容的 GET,以及 POST 检查订单是否成功
- 这是开箱即用的,但有时会失去与
Stream Refused
的连接,特别是:org.apache.hc.core5.http2.H2StreamResetException: Stream refused
- 没有单独的调整似乎适用于所有具有可变延迟的网络环境,我可以找到
- 跟踪堆栈跟踪似乎表明流已经关闭,因此需要一种方法来保持它打开或不执行已经关闭的连接
if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
}
解决问题的一些尝试
- 尝试使用搜索引擎寻找答案,但 HTTPClient5 的搜索结果很少
- 尝试使用 official documentation 但这是稀疏的
- 将每条路线的最大连接数更改为减少的数量,将不活动验证或连接时间改为实时
- 不活动检查可能会修复 POST,但会停止某些事务的 GET
- 并且对一个 region/restaurant 的调整可能对 1 个有效然后对另一个中断,w/ 只有网络作为变量
PoolingAsyncClientConnectionManagerBuilder builder = PoolingAsyncClientConnectionManagerBuilder
.create()
.setTlsStrategy(getTlsStrategy())
.setMaxConnPerRoute(12)
.setMaxConnTotal(12)
.setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
.setConnectionTimeToLive(TimeValue.ofMinutes(2))
.build();
- 切换到具有不同超时的自定义 RequestConfig
private HttpClientContext getHttpClientContext() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.of(10, TimeUnit.SECONDS))
.setResponseTimeout(Timeout.of(10, TimeUnit.SECONDS))
.build();
HttpClientContext httpContext = HttpClientContext.create();
httpContext.setRequestConfig(requestConfig);
return httpContext;
}
用于分析的初始代码段
(除了上述片段外,还有更改尝试)
- 初始化并获取响应的包装器处理
public SimpleHttpResponse getFullResponse(String url, PoolingAsyncClientConnectionManager manager, SimpleHttpRequest req) {
try (CloseableHttpAsyncClient httpclient = getHTTPClientInstance(manager)) {
httpclient.start();
CountDownLatch latch = new CountDownLatch(1);
long startTime = System.currentTimeMillis();
Future<SimpleHttpResponse> future = getHTTPResponse(url, httpclient, latch, startTime, req);
latch.await();
return future.get();
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
return new SimpleHttpResponse(999, CommonUtils.getExceptionAsMap(e).toString());
}
}
- 使用实际处理程序和探测代码
private Future<SimpleHttpResponse> getHTTPResponse(String url, CloseableHttpAsyncClient httpclient, CountDownLatch latch, long startTime, SimpleHttpRequest req) {
return httpclient.execute(req, getHttpContext(), new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse response) {
latch.countDown();
logger.info("[{}][{}ms] - {}", response.getCode(), getTotalTime(startTime), url);
}
@Override
public void failed(Exception e) {
latch.countDown();
logger.error("[{}ms] - {} - {}", getTotalTime(startTime), url, e);
}
@Override
public void cancelled() {
latch.countDown();
logger.error("[{}ms] - request cancelled for {}", getTotalTime(startTime), url);
}
});
}
直接提问
- 有没有办法配置客户端,使其可以自行处理这些差异,而无需显式修改每个端点上下文的配置?
已修复,结合以下内容以确保连接 Live/Ready
(或者至少是稳定的)
强制 HTTP 1
HttpAsyncClients.custom()
.setConnectionManager(manager)
.setRetryStrategy(getRetryStrategy())
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.setConnectionManagerShared(true);
为 POST
设置有效 Headers- 特别是收盘价header
req.setHeader("Connection", "close, TE");
- 注意:闲置检查有帮助,但有时仍会遭到拒绝 w/o this
按类型设置不活动检查
- 设置 POSTs 以在不活动后立即验证
- 注意:两者都使用 1000 导致某些系统的掉率很高
PoolingAsyncClientConnectionManagerBuilder
.create()
.setValidateAfterInactivity(TimeValue.ofMilliseconds(0))
- 设置GET在1秒后生效
PoolingAsyncClientConnectionManagerBuilder
.create()
.setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
给定错误上下文
- 跟踪堆栈跟踪中的连接问题到 AbstractH2StreamMultiplexer
- 显示 ConnectionHandshake.GRACEFUL_SHUTDOWN 触发流拒绝
if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
}
- 对应
connState = streamMap.isEmpty() ? ConnectionHandshake.SHUTDOWN : ConnectionHandshake.GRACEFUL_SHUTDOWN;
推理
- 如果我没理解错的话:
- 连接正在 un/intentionally 关闭
- 但是,在再次执行之前没有确认它们准备就绪
- 这导致它失败,因为流不可行
- 因此修复有效,因为(看起来)
- 给定强制 HTTP1 允许管理单个上下文
- 其中 HttpVersionPolicy NEGOTIATE/FORCE_HTTP_2 在 regions/menus 范围内有更大或同等的失败
- 并确保所有连接在使用前都有效
- 而POST由于关闭header而一直关闭,这对于HTTP2 是不可用的
- 因此
- GET 以合理的周期检查有效性 每次都检查
- POST,由于是强行关闭,所以在执行前是re-acquired
- 这没有为意外关闭留下空间
- 否则可能会错误地切换到 HTTP2
- 给定强制 HTTP1 允许管理单个上下文
- 连接正在 un/intentionally 关闭
会接受这个,直到出现更好的答案,因为这是稳定的,但 sub-optimal。