grpc server从client中获取超时信息
在grpc server中进行超时控制,需要感知客户端的超时时间。本文将对grpc client
,http
两种访问方式下如何给grpc server
传递超时时间进行总结。
在client
发起调用时,通过使用带timeout的context
,可将超时信息传递给server
。
- client端
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
go func() {
time.Sleep(1 * time.Second)
cancel()
}()
if err := client.SayHello(ctx, &hello.SayHelloRequest{
Message: "hello world",
}); err != nil {
logger.Error(ctx, "SayHello", zap.Error(err))
return
}
- server端
func doSomething(somethingDone chan<- struct{}) {
time.Sleep(10 * time.Second)
somethingDone <- struct{}{}
}
// inside handler
if ddl, ok := ctx.Deadline(); ok {
log.Printf("deadline set: %+v", ddl)
}
somethingDone := make(chan struct{})
go doSomething(somethingDone)
// set timeout to 2 seconds on client side
for {
select {
case <-ctx.Done():
log.Printf("!!! context cancel")
return nil, ctx.Err()
case <-somethingDone:
goto after
default:
}
}
after:
return &hello.SayHelloResponse{Message: r.Message}, nil
- client输出
2023/08/27 21:10:22 ### ready to invoke SayHello ###
{"level":"error","ts":"2023-08-27T21:10:23.496+0800","caller":"logger/interface.go:21","msg":"SayHello","error":"rpc error: code = Canceled desc = context canceled","stacktrace":"github.com/brickzzhang/grpc-helloworld/workshop/logger.Error\n\t/Users/bytedance/personalspace/grpc-helloworld/workshop/logger/interface.go:21\nmain.main\n\t/Users/bytedance/personalspace/grpc-helloworld/client/client.go:61\nruntime.main\n\t/opt/homebrew/Cellar/go/1.21.0/libexec/src/runtime/proc.go:267"}
- server输出
2023/08/27 21:10:22 deadline set: 2023-08-27 21:10:24.496345 +0800 CST m=+458.621381042
2023/08/27 21:10:23 !!! context cancel
{"level":"error","ts":"2023-08-27T21:10:23.497+0800","caller":"zap/server_interceptors.go:40","msg":"finished unary call with code Unknown","peer.address":"127.0.0.1:49311","grpc.start_time":"2023-08-27T21:10:22+08:00","grpc.request.deadline":"2023-08-27T21:10:24+08:00","system":"grpc","span.kind":"server","grpc.service":"helloworld.HelloWorldService","grpc.method":"SayHello","peer.address":"127.0.0.1:49311","error":"context canceled","grpc.code":"Unknown","grpc.time_ms":998.697021484375,"stacktrace":"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/server_interceptors.go:40\nmain.startServer.ChainUnaryServer.func6.1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:34\ngithub.com/grpc-ecosystem/go-grpc-middleware/tags.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/tags/interceptors.go:21\nmain.startServer.ChainUnaryServer.func6.1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:34\ngithub.com/grpc-ecosystem/go-grpc-middleware/recovery.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/recovery/interceptors.go:29\nmain.startServer.ChainUnaryServer.func6\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:39\ngithub.com/brickzzhang/grpc-helloworld/apigen/hello._HelloWorldService_SayHello_Handler\n\t/Users/bytedance/personalspace/grpc-helloworld/apigen/hello/helloworld_grpc.pb.go:267\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:1283\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:1620\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:922"}
使用grpc方式调用时,当client端的context
设置过超时信息,超时信息会通过context
传到server端。
- 调用方
curl localhost:8081/v1/helloworld -d '{"message": "test"}' -H 'grpc-timeout:1S' -X POST
- server端代码
func doSomething(somethingDone chan<- struct{}) {
time.Sleep(10 * time.Second)
somethingDone <- struct{}{}
}
// inside handler
if ddl, ok := ctx.Deadline(); ok {
log.Printf("deadline set: %+v", ddl)
}
somethingDone := make(chan struct{})
go doSomething(somethingDone)
// set timeout to 2 seconds on client side
for {
select {
case <-ctx.Done():
log.Printf("!!! context cancel")
return nil, ctx.Err()
case <-somethingDone:
goto after
default:
}
}
after:
return &hello.SayHelloResponse{Message: r.Message}, nil
- client输出
{"code":4,"data":null,"error":"context deadline exceeded"}%
- server输出
2023/08/27 22:10:34 deadline set: 2023-08-27 22:10:35.685275 +0800 CST m=+2903.421145001
2023/08/27 22:10:35 !!! context cancel
{"level":"error","ts":"2023-08-27T22:10:35.686+0800","caller":"zap/server_interceptors.go:40","msg":"finished unary call with code Unknown","peer.address":"127.0.0.1:49198","grpc.start_time":"2023-08-27T22:10:34+08:00","grpc.request.deadline":"2023-08-27T22:10:35+08:00","system":"grpc","span.kind":"server","grpc.service":"helloworld.HelloWorldService","grpc.method":"SayHello","peer.address":"127.0.0.1:49198","error":"context deadline exceeded","grpc.code":"Unknown","grpc.time_ms":1000.969970703125,"stacktrace":"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/server_interceptors.go:40\nmain.startServer.ChainUnaryServer.func6.1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:34\ngithub.com/grpc-ecosystem/go-grpc-middleware/tags.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/tags/interceptors.go:21\nmain.startServer.ChainUnaryServer.func6.1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:34\ngithub.com/grpc-ecosystem/go-grpc-middleware/recovery.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/recovery/interceptors.go:29\nmain.startServer.ChainUnaryServer.func6\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:39\ngithub.com/brickzzhang/grpc-helloworld/apigen/hello._HelloWorldService_SayHello_Handler\n\t/Users/bytedance/personalspace/grpc-helloworld/apigen/hello/helloworld_grpc.pb.go:267\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:1283\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:1620\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:922"}
当通过http方式,可通过添加grpc-timeout
header将超时时间传递给server,具体格式可参考grpc doc
当客户端连接断开时,服务端也会立即收到超时信号。
- 调用方
curl localhost:8081/v1/helloworld -d '{"message": "test"}' -X POST
^C
- server端代码
func doSomething(somethingDone chan<- struct{}) {
time.Sleep(10 * time.Second)
somethingDone <- struct{}{}
}
// inside handler
if ddl, ok := ctx.Deadline(); ok {
log.Printf("deadline set: %+v", ddl)
}
somethingDone := make(chan struct{})
go doSomething(somethingDone)
// set timeout to 2 seconds on client side
for {
select {
case <-ctx.Done():
log.Printf("!!! context cancel")
return nil, ctx.Err()
case <-somethingDone:
goto after
default:
}
}
after:
return &hello.SayHelloResponse{Message: r.Message}, nil
- server输出
2023/08/27 22:22:31 !!! context cancel
{"level":"error","ts":"2023-08-27T22:22:31.627+0800","caller":"zap/server_interceptors.go:40","msg":"finished unary call with code Unknown","peer.address":"127.0.0.1:49198","grpc.start_time":"2023-08-27T22:22:30+08:00","system":"grpc","span.kind":"server","grpc.service":"helloworld.HelloWorldService","grpc.method":"SayHello","peer.address":"127.0.0.1:49198","error":"context canceled","grpc.code":"Unknown","grpc.time_ms":888.677001953125,"stacktrace":"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/server_interceptors.go:40\nmain.startServer.ChainUnaryServer.func6.1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:34\ngithub.com/grpc-ecosystem/go-grpc-middleware/tags.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/tags/interceptors.go:21\nmain.startServer.ChainUnaryServer.func6.1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:34\ngithub.com/grpc-ecosystem/go-grpc-middleware/recovery.UnaryServerInterceptor.func1\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/recovery/interceptors.go:29\nmain.startServer.ChainUnaryServer.func6\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:39\ngithub.com/brickzzhang/grpc-helloworld/apigen/hello._HelloWorldService_SayHello_Handler\n\t/Users/bytedance/personalspace/grpc-helloworld/apigen/hello/helloworld_grpc.pb.go:267\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:1283\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:1620\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/Users/bytedance/personalspace/grpc-helloworld/vendor/google.golang.org/grpc/server.go:922"}
当client端连接断开时,server端同样会收到超时信号,可通过context
捕获处理。
Demo代码可参考grpc-helloworld