我们在进行服务间调用时广泛采用 gRPC 作为主要的调用协议,借助 gRPC 的模块化与语言无关的特性,可以在我们拓展多语言模块之间提供更好的支持。但是我们在使用 gRPC 之中也出现了一些问题,这些问题会做一些记录,希望可以与大家一起沟通与交流。
某日,我们的客服反馈,我们的基础设施操作工具出现了长时间无响应的问题。该问题出现在我们对某些设备进行 OTA 升级时,操作长时间无返回,与之前预期的 10 秒内返回存在较大出入。经过我们的工程师分析,我们发现在 gRPC 处理过程中,我们的操作工具通过 gRPC 调用远程服务端接口时,接口长时间没有返回结果。
我们首先怀疑是 gRPC 调用过程中出现了连接问题。gRPC 过程中可能由于多种原因导致连接断开或者服务器无法连接。在调用 gRPC 方法过程中,我们可以通过 FailFast(true)
方式进行快速失败。实际上,这个值默认情况下为 true
。
那么接下来我们就需要从调用从使用角度上寻找问题。我们使用过程中默许服务端在处理某些操作时进行较长时间操作(如长时间操作),但是从客户端角度而言,部分操作正常情况下我们是希望可以在有预定特定环境下达到某些时间仍旧未返回结果可以标记为结果失败。这样就需要通过 gRPC 的机制进行调控。由于目前我们的接口很多情况下调用接口实际为硬件接口,因此,我们采用通过控制 gRPC 客户端接口超时的方法控制。
在 gRPC 中,提供了 MethodConfig 用于控制每个方法的超时时间,这样可以对不同的 RPC 方法设置超时。
下面,我们用 官方的 gRPC 示例 演示如何进行调用超时控制。
首先,我们在 examples/helloworld/greeter_server/main.go
中的 SayHello
中添加一个长时间操作模拟:time.Sleep(10*time.Second)
。
这时,如果我们需要客户端在 5 秒以内返回结果,应该如何操作呢?
那么我们修改 examples/helloworld/greeter_client/main.go
中的 main
,添加超时处理内容:
func main() {
// Set up method timeout configure.
var wg sync.WaitGroup
wg.Add(1)
ch := make(chan grpc.ServiceConfig)
go func() {defer wg.Done()
mc := grpc.MethodConfig{
WaitForReady: true,
Timeout: 5 * time.Second,
}
m := make(map[string]grpc.MethodConfig)
// 格式:/PackageName/ServiceName/MethodName
m["/helloworld.Greeter/SayHello"] = mc
sc := grpc.ServiceConfig{Methods: m,}
ch <- sc}()
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {name = os.Args[1]
}
log.Printf("Starting...\n") // 明确执行时间
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
其中根据 MethodConfig 对应 map 结构,可以找到对应方法,对指定方法设置超时时间。最后,执行结果如下:
2017/05/19 15:59:11 Starting call...
2017/05/19 15:59:17 could not greet: rpc error: code = DeadlineExceeded desc = context deadline exceeded