gRPC 和截止时间
TL;DR:始终设置截止时间。本文解释了为什么我们建议有意设置截止时间,并提供有用的代码片段来演示如何操作。
当您使用 gRPC 时,gRPC 库会处理通信、编组、解组和截止时间强制执行。截止时间允许 gRPC 客户端指定他们愿意等待 RPC 完成的时间,超过此时间 RPC 将以错误 DEADLINE_EXCEEDED
终止。默认情况下,此截止时间是一个非常大的数字,具体取决于语言实现。如何指定截止时间也取决于语言。一些语言 API 使用截止时间,即 RPC 应完成的固定时间点。其他语言则使用超时,即 RPC 超时的时间段。
通常,当您不设置截止时间时,所有正在运行的请求都会占用资源,并且所有请求都可能达到最大超时时间。这会使服务面临耗尽资源(如内存)的风险,从而增加服务的延迟,或者在最坏的情况下可能导致整个进程崩溃。
为了避免这种情况,服务应指定其技术上支持的最长默认截止时间,客户端应等待直到响应对他们不再有用为止。对于服务而言,这可以像在 .proto 文件中提供注释一样简单。对于客户端而言,这涉及设置有用的截止时间。
对于“什么是好的截止时间/超时值?”这个问题,没有一个统一的答案。您的服务可能像我们快速入门指南中的Greeter一样简单,在这种情况下,100 毫秒就足够了。您的服务可能像一个全球分布式且强一致的数据库一样复杂。客户端查询的截止时间将不同于他们应该等待您删除其表的时间。
那么,您需要考虑哪些因素才能对截止时间做出明智的选择?需要考虑的因素包括整个系统的端到端延迟、哪些 RPC 是串行的以及哪些可以并行执行。您应该能够用数字表示它,即使只是粗略的计算。工程师需要了解服务,然后为客户端和服务器之间的 RPC 设置明确的截止时间。
在 gRPC 中,客户端和服务器都会独立地在本地判断远程过程调用 (RPC) 是否成功。这意味着他们的结论可能不匹配!在服务器端成功完成的 RPC 在客户端可能会失败。例如,服务器可以发送响应,但在客户端的截止时间到期后,回复可能会到达客户端。客户端将已经以状态错误 DEADLINE_EXCEEDED
终止。应该在应用程序级别检查和管理此情况。
设置截止时间
作为客户端,您应始终设置截止时间,以确定您愿意等待服务器回复的时间。以下是使用快速入门页面中的 Greeting 服务的示例
C++
ClientContext context;
time_point deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(100);
context.set_deadline(deadline);
Go
clientDeadline := time.Now().Add(time.Duration(*deadlineMs) * time.Millisecond)
ctx, cancel := context.WithDeadline(ctx, clientDeadline)
Java
response = blockingStub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).sayHello(request);
这会将截止时间设置为从客户端 RPC 设置到客户端接收到响应时的 100 毫秒。
检查截止时间
在服务器端,服务器可以查询以查看是否不再需要特定的 RPC。在服务器开始处理响应之前,检查是否仍有客户端在等待非常重要。这在开始昂贵的处理之前尤其重要。
C++
if (context->IsCancelled()) {
return Status(StatusCode::CANCELLED, "Deadline exceeded or Client cancelled, abandoning.");
}
Go
if ctx.Err() == context.Canceled {
return status.New(codes.Canceled, "Client cancelled, abandoning.")
}
Java
if (Context.current().isCancelled()) {
responseObserver.onError(Status.CANCELLED.withDescription("Cancelled by client").asRuntimeException());
return;
}
当您知道您的客户端已达到其截止时间时,服务器继续处理请求是否有用?这取决于情况。如果响应可以缓存在服务器中,那么处理并缓存它可能是值得的;尤其是在资源密集型且每次请求都需要花费资金的情况下。这将使未来的请求更快,因为结果已经可用。
调整截止时间
如果您设置了截止时间,但是新的版本或服务器版本导致了不良的回归怎么办?截止时间可能太小,导致所有请求都因 DEADLINE_EXCEEDED
而超时,或者太大,导致您的用户尾部延迟现在非常大。您可以使用标志来设置和调整截止时间。
C++
#include <gflags/gflags.h>
DEFINE_int32(deadline_ms, 20*1000, "Deadline in milliseconds.");
ClientContext context;
time_point deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(FLAGS_deadline_ms);
context.set_deadline(deadline);
Go
var deadlineMs = flag.Int("deadline_ms", 20*1000, "Default deadline in milliseconds.")
ctx, cancel := context.WithTimeout(ctx, time.Duration(*deadlineMs) * time.Millisecond)
Java
@Option(name="--deadline_ms", usage="Deadline in milliseconds.")
private int deadlineMs = 20*1000;
response = blockingStub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).sayHello(request);
现在,可以调整截止时间,使其等待更长时间以避免失败,而无需挑选具有不同硬编码截止时间的版本。这使您可以在调试和解决回归问题之前为用户缓解问题。