apache-httpclient-5.x - Apache HTTPClient5 - 如何防止连接/流被拒绝
问题描述
问题陈述
语境
- 我是一名软件工程师,正在测试运行餐厅菜单项的订单排列,以确认它们通过 POS 成功下单
- 简而言之,这会将 JSON 有效负载发布到端点,然后使用 POS 验证订单以定义成功/失败/其他
- POS 以及因此每秒事务数 (TPS) 可能会有所不同,但每个后端使用相同的核心处理
- 这可以高达每项约 22,000 个排列,以易于管理的 JSON 大小,需要尽快处理
- 网络可能因餐厅和/或地区而异,其中一个正在测试
- 例如,有些延迟比其他延迟高得多
- 因此,HTTPClient 应该能够智能地协商相同的内容和端点,而不管这一点
直接问题
- 我正在使用 Apache 的 HTTP Client 5 w/ PoolingAsyncClientConnectionManager 来执行菜单内容的 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 的点击率很少
- 试图使用官方文档,但这很少
- 将每条路线的最大连接数更改为减少的数量,将不活动验证或连接时间更改为有效
- 不活动检查可能会修复 POST,但会停止某些事务的 GET
- 并且对一个区域/餐厅的调整可能适用于 1 然后中断另一个,只有网络作为变量
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);
}
});
}
直接问题
- 有没有办法配置客户端,以便它可以自行处理这些差异,而无需显式修改每个端点上下文的配置?
解决方案
修复了以下内容的组合以确保连接实时/就绪
(或者至少是稳定的)
强制 HTTP 1
HttpAsyncClients.custom()
.setConnectionManager(manager)
.setRetryStrategy(getRetryStrategy())
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.setConnectionManagerShared(true);
为 POST 设置有效标头
- 特别是关闭标题
req.setHeader("Connection", "close, TE");
- 注意:不活动检查有帮助,但有时仍会被拒绝
按类型设置不活动检查
- 将 POST 设置为在不活动后立即验证
- 注意:两者都使用 1000 会导致某些系统的掉率较高
PoolingAsyncClientConnectionManagerBuilder
.create()
.setValidateAfterInactivity(TimeValue.ofMilliseconds(0))
- 将 GET 设置为 1 秒后验证
PoolingAsyncClientConnectionManagerBuilder
.create()
.setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
给定错误上下文
- 将stacktrace中的连接问题跟踪到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;
推理
- 如果我理解正确:
- 连接被非/有意关闭
- 但是,在再次执行之前,它们并没有被确认准备好
- 这导致它失败,因为流不可行
- 因此修复有效,因为(似乎)
- 鉴于强制 HTTP1 允许管理单个上下文
- HttpVersionPolicy NEGOTIATE/FORCE_HTTP_2 在区域/菜单的范围内有更大或等效的故障
- 并确保所有连接在使用前都是有效的
- 并且由于 HTTP2 不可用的 close 标头,POST 总是关闭
- 所以
- 使用合理的周期性检查 GET 的有效性
- 每次都检查POST,由于是强制关闭,所以在执行前重新获取
- 这没有为意外关闭留下空间
- 否则可能会错误地切换到 HTTP2
- 鉴于强制 HTTP1 允许管理单个上下文
- 连接被非/有意关闭
将接受这一点,直到出现更好的答案,因为这是稳定但次优的。
推荐阅读
- sql-server - 远程主机关闭分析服务连接(Azure SQL 数据库)
- javascript - 如何在 Node/Express 中启用 OAuth 而无需维护会话
- python - “选项”对象没有属性“get_all_related_objects”
- node.js - Elasticsearch APM 服务器的活动句柄过多
- security - 如何防止来自网站服务器的重复请求
- c# - 如何通过 web.config 或 IIS 设置从 Cache-Control 中删除私有
- c# - 如何处理 ews 挂起?
- java - 如何通过 docker-compose.yml 文件连接两个 SCS 和一个代理服务器?
- php - jquery在while循环中使用引导模式更改功能
- c - 如何强制链接器将字符串数组的 const 数组存储到闪存中(而不是 RAM)