1. gRPC service timeout setting
The default request timeout of gRPC is very long. When you do not set the request timeout, all running requests will occupy a lot of resources and may
Running for a long time will lead to excessive service resource consumption, making subsequent request responses too slow, and even causing the entire process to crash.
To avoid this, our service should set a timeout. The previous introductory tutorial mentioned that when the client initiates a request, it needs to pass in the upper and lower
Text context.Context
, used to end timeout
or cancel
request.
This article introduces how to set the timeout period for gRPC
requests.
1.1 proto file and compilation
syntax = "proto3"; package proto; option go_package = "./proto;proto"; // Define our services (multiple services can be defined, and each service can define multiple interfaces) service Simple{ rpc Route (SimpleRequest) returns (SimpleResponse){}; } message SimpleRequest{ // Define the parameters to be sent, using camel case naming method, lowercase and underlined, such as: student_name string data = 1;//Send data } message SimpleResponse{ //Define the parameters received //Parameter type Parameter name Identification number (not repeatable) int32 code = 1; //Status code string value = 2;//Receive value }
protoc --go_out=plugins=grpc:.simple.proto
1.2 Client request setting timeout
Modify the calling server method:
1. Set the timeout to the current time + 3 seconds
clientDeadline := time.Now().Add(time.Duration(3 * time.Second)) ctx, cancel := context.WithDeadline(ctx, clientDeadline) defer cancel()
2. Add timeout detection in response error detection
//Input ctx with a timeout of 3 seconds res, err := grpcClient.Route(ctx, & amp;req) if err != nil {<!-- --> //Get error status status, ok := status.FromError(err) if ok {<!-- --> //Determine whether the call has timed out if statu.Code() == codes.DeadlineExceeded {<!-- --> log.Fatalln("Route timeout!") } } log.Fatalf("Call Route err: %v", err) } // print return value log.Println(res.Value)
3. Complete client.go
code
package main import ( "context" pb "demo/proto" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "log" "time" ) // Address connection address const Address string = ":8000" var grpcClient pb.SimpleClient func main() {<!-- --> \t// connect to the server conn, err := grpc.Dial(Address, grpc.WithInsecure()) if err != nil {<!-- --> log.Fatalf("net.Connect err: %v", err) } defer conn.Close() ctx := context.Background() // Establish gRPC connection grpcClient = pb.NewSimpleClient(conn) //Set 2 seconds timeout route(ctx, 2) } //route calls the server Route method func route(ctx context.Context, deadlines time.Duration) {<!-- --> //Set the timeout clientDeadline := time.Now().Add(time.Duration(deadlines * time.Second)) ctx, cancel := context.WithDeadline(ctx, clientDeadline) defer cancel() //Create sending structure req := pb.SimpleRequest{<!-- --> Data: "grpc", } // Call our service (Route method) // Pass in the ctx of the timeout res, err := grpcClient.Route(ctx, & amp;req) if err != nil {<!-- --> //Get error status status, ok := status.FromError(err) if ok {<!-- --> //Determine whether the call has timed out if statu.Code() == codes.DeadlineExceeded {<!-- --> log.Fatalln("Route timeout!") } } log.Fatalf("Call Route err: %v", err) } // print return value log.Println(res.Value) }
1.3 The server determines whether the request has timed out
When the request times out, the server should stop ongoing operations to avoid wasting resources.
//Route implements the Route method func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {<!-- --> data := make(chan *pb.SimpleResponse, 1) go handle(ctx, req, data) select {<!-- --> case res := <-data: return res, nil case <-ctx.Done(): return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.") } } func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {<!-- --> select {<!-- --> case <-ctx.Done(): log.Println(ctx.Err()) runtime.Goexit() //Exit the Go coroutine after timeout case <-time.After(4 * time.Second): // Simulate time-consuming operations res := pb.SimpleResponse{<!-- --> Code: 200, Value: "hello " + req.Data, } // Make timeout judgment before modifying the database // If it has timed out, exit // if ctx.Err() == context.Canceled{} data <- &res } }
Generally, timeout detection is performed before writing to the library, and work is stopped when a timeout is found.
Complete server.go
code:
package main import ( "context" pb "demo/proto" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "log" "net" "runtime" "time" ) // SimpleService defines our service type SimpleService struct{<!-- -->} const ( // Address listening address Address string = ":8000" // Network network communication protocol Network string = "tcp" ) func main() {<!-- --> //Listen to the local port listener, err := net.Listen(Network, Address) if err != nil {<!-- --> log.Fatalf("net.Listen err: %v", err) } log.Println(Address + " net.Listing...") //Create a new gRPC server instance grpcServer := grpc.NewServer() // Register our service on the gRPC server pb.RegisterSimpleServer(grpcServer, & amp;SimpleService{<!-- -->}) //Use the server Serve() method and our port information area to implement blocking wait until the process is killed or Stop() is called err = grpcServer.Serve(listener) if err != nil {<!-- --> log.Fatalf("grpcServer.Serve err: %v", err) } } //Route implements the Route method func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {<!-- --> data := make(chan *pb.SimpleResponse, 1) go handle(ctx, req, data) select {<!-- --> case res := <-data: log.Println("Return information 1!") return res, nil case <-ctx.Done(): log.Println("Return information 2!") return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.") } } func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {<!-- --> select {<!-- --> case <-ctx.Done(): log.Println("err:", ctx.Err()) //Exit the Go coroutine after timeout runtime.Goexit() // Simulate time-consuming operations case <-time.After(3 * time.Second): log.Println("time") res := pb.SimpleResponse{<!-- --> Code: 200, Value: "hello " + req.Data, } // Make timeout judgment before modifying the database // If it has timed out, exit // if ctx.Err() == context.Canceled{} data <- &res } }
# Project structure $ tree demo/ demo/ ├── client.go ├── go.mod ├── go.sum ├── prototype │ └── simple.pb.go ├── server.go └── simple.proto 1 directory, 6 files
1.4 Operation results
Server:
[root@zsx demo]# go run server.go 2023/02/13 10:22:59 :8000 net.Listing... 2023/02/13 10:23:07 Return information 2! 2023/02/13 10:23:07 err: context deadline exceeded
Client:
[root@zsx demo]# go run client.go 2023/02/13 10:23:07 Route timeout! exit status 1
1.5 If the client sets a timeout of 5 seconds
//Set 5 seconds timeout route(ctx, 5)
[root@zsx demo]# go run server.go 2023/02/13 10:24:15 :8000 net.Listing... 2023/02/13 10:24:20 time 2023/02/13 10:24:20 Return information 1!
[root@zsx demo]# go run client.go 2023/02/13 10:24:20 hello grpc
1.6 Summary
The length of the timeout depends on the service itself. For example, returning a hello grpc may only take tens of milliseconds. However, while processing a large amount of data,
Step operations may take a long time. Many factors need to be considered to determine this timeout, such as the end-to-end delay between systems, which RPCs are serial
, which ones can be parallelized, etc.