我对 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
?如果两者都不是问题,那也有帮助;我会进一步挖掘。虽然,我对下面提到的部分相当有信心