指定截止时间
在处理异步通信时,截止时间(Deadlines) 是最重要的,因为由于网络或其他问题,调用可能永远不会返回。因此,Google 推荐我们为每个 RPC 调用设置截止时间。幸运的是,这与取消调用一样简单。
我们首先需要在客户端创建一个上下文。这类似于 WithCancel
函数,但这次我们将使用 WithTimeout
。它接受一个父上下文,类似于 WithCancel
,但是它额外接受一个 Time
实例,表示我们愿意等待服务器响应的最大时间。
在 printTasks
中,我们将不再使用 WithCancel
,而是使用如下的上下文:
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()
显然,1 毫秒的超时时间对于让服务器响应来说太低了,但这是故意设置的,以便我们能够得到一个 DeadlineExceeded
错误。在实际应用场景中,我们需要根据服务的需求来设置超时。这个时间设置非常依赖于你的用例和服务的工作内容,因此你需要进行实验并跟踪服务器响应的平均时间。
这就是在 gRPC 中实现超时所需的全部内容。现在,我们可以运行我们的服务器:
$ go run ./server 0.0.0.0:50051
listening at 0.0.0.0:50051
然后,我们运行客户端:
$ go run ./client 0.0.0.0:50051
//...
unexpected error: rpc error: code = DeadlineExceeded desc = context deadline exceeded
我们可以看到,第一个 ListTasks
请求如预期那样失败了。
现在,尽管这已经是设置超时所需的一切,但你还可以让服务器意识到 DeadlineExceeded
错误。即使这在技术上已经完成,因为我们在 Done
通道关闭时返回 ctx.Err()
,我们仍然希望打印一条消息,说明超时已经发生。
为此,您可以像处理 Canceled
错误一样,在 ctx.Err()
的 switch
语句中添加一个 DeadlineExceeded
分支:
func (s *server) ListTasks(req *pb.ListTasksRequest, stream pb.TodoService_ListTasksServer) error {
ctx := stream.Context()
return s.d.getTasks(func(t interface{}) error {
select {
case <-ctx.Done():
switch ctx.Err() {
//...
case context.DeadlineExceeded:
log.Printf("request deadline exceeded: %s", ctx.Err())
}
return ctx.Err()
//...
}
})
}
如果重新运行服务器和客户端,您将看到服务器端输出以下消息:
request deadline exceeded: context deadline exceeded
通过本节,我们学习了如何使用 WithTimeout
设置 gRPC 调用的截止时间。与 WithCancel
类似,WithTimeout
允许我们为调用创建一个超时限制。设置超时是非常重要的,因为如果我们不设置超时,可能永远不会从服务器获得响应。最后,我们还了解了如何使服务器支持超时,以确保它不会继续执行不必要的工作。