【问题标题】:Apache HTTPClient5 - How to Prevent Connection/Stream RefusedApache HTTPClient5 - 如何防止连接/流被拒绝
【发布时间】:2021-05-15 21:18:32
【问题描述】:

问题陈述

上下文

  • 我是一名软件工程师,正在测试运行餐厅菜单项的订单排列,以确认它们通过 POS 成功下单
    • 简而言之,这会将 JSON 有效负载 POST 到端点,然后使用 POS 验证订单以定义成功/失败/其他
    • POS 以及每秒事务数 (TPS) 可能会有所不同,但每个后端使用相同的核心处理
    • 这可能高达每项约 22,000 个排列,JSON 大小易于管理,需要尽快处理
    • 网络可能因餐厅和/或地区而异,其中一个正在测试中
      • 例如其中一些的延迟比其他的高得多
    • 因此,不管如何,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
    • 对于一个区域/餐厅的调整可能适用于 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);
                }

            });
        }

直接问题

  • 有没有办法配置客户端,使其可以自行处理这些差异,而无需显式修改每个端点上下文的配置?

【问题讨论】:

    标签: apache-httpclient-5.x


    【解决方案1】:

    修复了以下内容以确保连接实时/就绪

    (或者至少是稳定的)

    强制 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))
    

    给定错误上下文

    • 将堆栈跟踪中的连接问题跟踪到 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 在整个区域/菜单范围内出现更大或相当的故障
        • 并保证所有连接在使用前都是有效的
        • 并且由于关闭头,POST总是关闭,HTTP2不可用
        • 因此
          • 通过合理的周期性检查 GET 的有效性
          • POST每次都会检查,由于是强制关闭,所以在执行前重新获取
          • 不会为意外关闭留下空间
            • 否则可能会错误地切换到 HTTP2

    会接受这一点,直到出现更好的答案,因为这是稳定但次优的。

    【讨论】:

      猜你喜欢
      • 2017-09-01
      • 2018-02-11
      • 2011-08-07
      • 2020-10-01
      • 2016-11-11
      • 1970-01-01
      • 2017-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多