GRPC 客户端负载均衡

时间:2021-02-20 18:22:21

标签: multithreading kubernetes grpc

我对 GRPC 很陌生,所以这个问题可能非常幼稚。请耐心等待。

总结

上下文是有一个使用 Kubernetes 启动的服务。我们对另一个 K8s 服务进行并发 GRPC 调用。我们注意到来自任何 Pod 的第一次调用都需要很长时间,随后的调用速度非常快。当我们查看服务器端时,尽管我们将 round_robin 指定为客户端负载平衡,但似乎所有调用都将转到同一个 Pod。

有问题的代码片段

客户端中的关键方法是getResults,实现如下图(我去掉了很多可观察性,尝试catch使其更具可读性)

public void getResults(
            final Request request,
            ConcurrentHashMap<Integer,List<Integer>> resultMap,
            CountDownLatch countDownLatch
            ) {
            Timer.context timer = latency.time();
            ListenableFuture<Response>  response = futureStub
                    .withDeadlineAfter(1500, TimeUnit.MILLISECONDS)
                    .validateResults(request);
            Futures.addCallback(
                    response,
                    new FutureCallback<>() {
                        @Override
                        public void onSuccess(@Nullable Response response) {
                            long elapsedTime = timer.stop();
                            List<Integer> invalidIds = getInvalidIdsFromResponse(response);
                            resultMap.put((int) request.getId().getValue(), invalidIds);
                            long latencyInMs = elapsedTime / 1000000;
                            if (latencyInMs >= 1000) {
                                LOGGER.warn("[TimeConsumingCall] - " + request + " took " + latencyInMs + " ms ");
                            }
                            countDownLatch.countDown();
                        }

                        @Override
                        public void onFailure(Throwable t) {
                            if (Status.fromThrowable(t).getCode().equals(Status.Code.DEADLINE_EXCEEDED)) {
                                deadline.mark(1);
                            }
                        }
                    },
                    MoreExecutors.directExecutor()
            );
}

getResults 方法的调用方式为

    public HashMap<Integer, List<Integer>> getInvalidResults(List<Request> requests) {
        if(requests == null || requests.isEmpty()) {
            return new HashMap<>();
        }
        final CountDownLatch countDownLatch = new CountDownLatch(requests.size());
        ConcurrentHashMap<Integer, List<Integer>> invalidIds = new ConcurrentHashMap<>();
        for(Request request: requests){
            executorService.submit(() -> {
                getResults(request, invalidIds, countDownLatch);
            });
        }
        try {
            countDownLatch.await(2000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            LOGGER.warn("Incomplete results returned - ", e);
        }
     }

,典型的请求长度在 50 左右。这就是 executor 服务

private final transient ExecutorService executorService = new ThreadPoolExecutor(200, 500, 300,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(1500),
            new ThreadFactoryBuilder().setNameFormat("Validation-%d").build(),
            (r, executor) -> LOGGER.error("Discarding Parallel Call:  queue is full"));

我们使用的是单例未来存根,它是使用这样创建的通道构建的

ManagedChannelBuilder.forTarget(
                Environment.getValue(dns:///<HOST>:<PORT>)
                        .defaultLoadBalancingPolicy("round_robin")
                        .enableRetry()
                        .maxRetryAttempts(2)
                        .usePlaintext()
                        .build()

实际情况是,尽管我们设置了大得离谱的超时(1.5 秒和 2 秒)(服务的 P99 为 120 毫秒),但它们始终受到攻击。此外,我们将看到其中一个 Pod 在服务器端出现峰值(大约 1 - 2 秒),然后事情稳定下来,我们得到 <100 毫秒的延迟。为了调试,我们在标头中添加了拦截器,发现来自客户端的大部分请求都将发送到一个 Pod,这很可能会触发峰值(根本原因是 DB 问题,这是一个单独的问题)。我的问题是为什么请求没有均匀分布在服务器 pod 中?我是否在代码中犯了错误(执行者?渠道?)还是我只是没有正确理解 round_robin?如果两者都不是问题,那也有帮助;我会进一步挖掘。虽然,我对下面提到的部分相当有信心

0 个答案:

没有答案