重试调用
到目前为止,我们只在服务器端工作。现在,让我们看看客户端上的一个重要功能——根据状态码重试失败的调用。这在网络不稳定的情况下可能非常有用。如果我们遇到 Unavailable
错误码,我们将使用指数增长的等待时间进行重试。这样做是因为我们不希望过于频繁地重试,导致网络过载。
gRPC 本身支持重试,而无需第三方库。然而,配置过程相对冗长,且文档并不完善。如果您有兴趣尝试,可以查看以下示例: gRPC Go 示例。 |
接下来,我们需要获取所需的依赖(在 client
文件夹中):
$ go get github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry
然后,我们可以定义一些重试选项。我们将定义重试次数和在何种错误码下进行重试。我们希望重试 3 次,使用指数回退(从 100 毫秒开始),并且错误码为 Unavailable
:
retryOpts := []retry.CallOption{
retry.WithMax(3),
retry.WithBackoff(retry.BackoffExponential(100 * time.Millisecond)),
retry.WithCodes(codes.Unavailable),
}
接着,我们将这些选项传递给 retry
包提供的拦截器:
import (
//...
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
)
func main() {
//...
retryOpts := []retry.CallOption{
//...
}
opts := []grpc.DialOption{
//...
grpc.WithChainUnaryInterceptor(
retry.UnaryClientInterceptor(retryOpts...),
//...
),
grpc.WithChainStreamInterceptor(
retry.StreamClientInterceptor(retryOpts...),
//...
),
//...
}
//...
}
重试对于客户端流式 RPC 是不可用的。如果您尝试在这种 RPC 接口上进行重试,您将得到以下错误: |
一旦我们完成了这些配置,问题就来了。我们的 API 正在本地运行,并且几乎不可能遇到 Unavailable
错误。因此,为了测试和演示,我们将暂时让 AddTask
直接返回这样的错误。在 server/impl.go
中,我们可以注释掉其余的代码并添加以下内容:
func (s *server) AddTask(_ context.Context, in *pb.AddTaskRequest) (*pb.AddTaskResponse, error) {
return nil, status.Errorf(
codes.Unavailable,
"unexpected error: %s",
"unavailable",
)
}
然后,我们运行服务器:
$ go run ./server 0.0.0.0:50051 0.0.0.0:50052
metrics server listening at 0.0.0.0:50052
gRPC server listening at 0.0.0.0:50051
接着运行我们的客户端:
$ go run ./client 0.0.0.0:50051
--------ADD--------
rpc error: code = Unavailable desc = unexpected error: unavailable
我们得到一个错误。虽然看起来只进行了一个查询,但如果你查看服务器日志,应该能看到如下信息:
INFO :started call todo.v2.TodoService AddTask
WARN :finished call todo.v2.TodoService AddTask
INFO :started call todo.v2.TodoService AddTask
WARN :finished call todo.v2.TodoService AddTask
INFO :started call todo.v2.TodoService AddTask
WARN :finished call todo.v2.TodoService AddTask
实际上,这是进行了三次请求。
Bazel
和往常一样,您需要运行 gazelle-update-repos
和 gazelle
来获取新的依赖并将其链接到您的库:
$ bazel run //:gazelle-update-repos
$ bazel run //:gazelle
现在,您应该能够正确运行客户端:
$ bazel run //client:client 0.0.0.0:50051
--------ADD--------
rpc error: code = Unavailable desc = unexpected error: unavailable
总结一下,在这一节中,我们展示了如何根据某些条件进行重试,使用指数回退,并且设定重试的次数。重试是一个重要的功能,因为网络通常不可靠,我们不希望每次出现问题时都让用户手动重试。