gRPC client 如何实现 TCP 重连

之前写过一篇 gRPC-go 建立 TCP 连接的过程 博客,主要研究了 client 程序启动后,如何与 server 建立 TCP 连接。 今天,在思考 redis-go 的连接池实现的时候,突然想到: 当 gRPC 的 TCP 连接断开后,能自动重连吗? 如果可以,是如何实现的 ? 首先要注意,这里指的是 TCP 连接,而不是 http2 中的 stream。 我们知道,gRPC 数据的传输使用 http2 的多路复用,也就是在一个 TCP 连接上有多个全双工的 http2 stream,这里的 stream 如果被断开后怎么重连与 http2 的实现有关,不在本文讨论范围。 对于上面第一个问题,使用 gRPC 的经验告诉我是可以自动重连的,不妨再做个简单的测试,client 端代码如下: func main() { conn, _ := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure()) defer conn.Close() cli := protobuf.NewTestClient(conn) req := &protobuf.EchoRequest{ Msg: "hi", } for i := 0; i < 10000; i++ { time.Sleep(time.Second) resp, err := cli.Echo(context.Background(), req) if err != nil { log.Printf("%v\n", err) continue } log.Printf("[D] resp: %s", resp.Reply) } } server 端代码略。 启动 client 后,不断启动和 ctrl-c 结束 server,证实 client 能自动重连 TCP 。 使用 netstat 查看 TCP 连接也能看到 client 使用了新的端口号重连。 ...

2022-04-04 · Me

gRPC-go 建立 TCP 连接的过程

首先看一个最简单的建立 client server 之间 gRPC 连接的代码,以这个代码为例,分析一下 TCP 是在何时建立的。 Server 端的代码相对来说很容易,一个最简单的 server 代码如下: func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 8080)) grpcServer := grpc.NewServer() protobuf.RegisterTestServer(grpcServer, &server{}) grpcServer.Serve(lis) } 在 grpc/server.go 中的 Serve() 函数调用了 lis.Accept() 并阻塞,当 client 端发来 TCP 请求时,Accept() 返回 Conn 结构,并开启 goroutine handleRawConn() 进行后续的处理。 就 TCP 来说,server 端的代码简单易懂,相比之下 client 端则不一样,一个基本的 Client 代码如下: func main() { conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) defer conn.Close() cli := protobuf.NewTestClient(conn) } 而要弄清楚 Client 端如何建立 TCP 却不容易,这是因为 grpc client 有 resolve DNS 以及做 load balancer 的功能,因此代码复杂很多。 从上面的代码不难看出肯定是在 Dial() 函数中建立的,它的具体实现是在 DialContext(), 返回 ClientConn 结构体指针,但是却看不到在哪建立了 TCP 链接,这是因为 TCP 链接是在一个 Goroutine 中异步建立的。如果想要 DialContext() 等连接建立完再返回,可以指定grpc.WithBlock()传入Options来实现。 ...

2020-10-11 · Me