GRPC中设置client的超时时间(golang)

在使用grpc的时候遇到了一个问题: 如何设置client端的超时时间? 网上搜了一大圈, 没有太明显的例子.

这里我们先看下看看grpc的helloworld例子:

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
        // 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]
        }
        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)
}

稍微跟一下SayHello的调用, 最后是调用到invoke函数:

google.golang.org/grpc/call.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (e error) {
    c := defaultCallInfo
    mc := cc.GetMethodConfig(method)
    if mc.WaitForReady != nil {
        c.failFast = !*mc.WaitForReady
    }

    // 值得注意
    if mc.Timeout != nil && *mc.Timeout >= 0 {
        var cancel context.CancelFunc
        ctx, cancel = context.WithTimeout(ctx, *mc.Timeout)
        defer cancel()
    }

    opts = append(cc.dopts.callOptions, opts...)
    for _, o := range opts {
        if err := o.before(&c); err != nil {
            return toRPCErr(err)
        }
    }
    defer func() {
        for _, o := range opts {
            o.after(&c)
        }
    }()

这里值得注意的是context的超时设置. 仔细读下context的文档, 会发现context里面有对应的超时设置:

WithTimeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Pass a context with a timeout to tell a blocking function that it
    // should abandon its work after the timeout elapses.
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    }
}

我们尝试在client调用之前设置context的超时:

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
        // 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]
        }

        // 改一下
        ctx, cancel := context.WithTimeout(context.Background(), 100 * time.Millisecond)
        defer cancel()
        r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
        if err != nil {
                log.Fatalf("could not greet: %v", err)
        }
        log.Printf("Greeting: %s", r.Message)
}

为保证超时, 在sever代码中休息一会儿:

server
1
2
3
4
5
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        time.Sleep(1 * time.Second)
        return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

分别运行client和server两个程序, 会看见client端打印:

1
could not greet: rpc error: code = DeadlineExceeded desc = context deadline exceeded

扩展阅读:

go, grpc

Comments