问题描述
问题说明
上下文
- 我是一名软件工程师,负责测试运行餐厅菜单项的订单排列,以确认它们通过 POS 成功下单
直接问题
- 我正在使用带有 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 但这很稀疏
- 将每条路由的最大连接数更改为减少数量、转移不活动验证或连接存活时间
PoolingAsyncclientConnectionManagerBuilder builder = PoolingAsyncclientConnectionManagerBuilder
.create()
.setTlsstrategy(getTlsstrategy())
.setMaxConnPerRoute(12)
.setMaxConnTotal(12)
.setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
.setConnectionTimetoLive(TimeValue.ofMinutes(2))
.build();
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] - {} - {}",url,e);
}
@Override
public void cancelled() {
latch.countDown();
logger.error("[{}ms] - request cancelled for {}",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;
推理
- 如果我理解正确:
- 连接被无意/有意关闭
- 然而,在再次执行之前,它们并没有被确认准备好
- 这导致它失败,因为流不可行
- 因此修复有效,因为(似乎)
- Given Forcing HTTP1 允许管理单个上下文
- HttpVersionPolicy NEGOTIATE/FORCE_HTTP_2 在整个区域/菜单范围内出现更大或同等失败的情况
- 并确保所有连接在使用前均有效
- 并且 POST 由于关闭标头而始终关闭,这对 HTTP2 不可用
- 因此
- 以合理的周期检查 GET 的有效性
- POST 每次都检查,由于是强行关闭,执行前重新获取
- 没有任何意外关闭的余地
- 否则可能会错误地切换到 HTTP2
- Given Forcing HTTP1 允许管理单个上下文
- 连接被无意/有意关闭
将接受这个直到出现更好的答案,因为这是稳定但次优的。