单单会用还不行,我们再深剖一下,看看它是怎么实现的。核心代码如下:
func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor { n := len(interceptors) if n > 1 { lastI := n - 1 return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { var ( chainHandler grpc.UnaryInvoker curI int ) chainHandler = func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error { if curI == lastI { return invoker(currentCtx, currentMethod, currentReq, currentRepl, currentConn, currentOpts...) } curI++ err := interceptors[curI](currentCtx, currentMethod, currentReq, currentRepl, currentConn, chainHandler, currentOpts...) curI-- return err } return interceptors[0](ctx, method, req, reply, cc, chainHandler, opts...) } } ... }当拦截器数量大于 1 时,从 interceptors[1] 开始递归,每一个递归的拦截器 interceptors[i] 会不断地执行,最后才真正的去执行 handler 方法。同时也经常有人会问拦截器的执行顺序是什么,通过这段代码你得出结论了吗?
7. 频繁创建 ClientConn 有什么问题?这个问题我们可以反向验证一下,假设不公用 ClientConn 看看会怎么样?如下:
func BenchmarkSearch(b *testing.B) { for i := 0; i < b.N; i++ { conn, err := GetClientConn() if err != nil { b.Errorf("GetClientConn err: %v", err) } _, err = Search(context.Background(), conn) if err != nil { b.Errorf("Search err: %v", err) } } }输出结果:
... connection error: desc = "transport: Error while dialing dial tcp :10001: socket: too many open files" ... connection error: desc = "transport: Error while dialing dial tcp :10001: socket: too many open files" ... connection error: desc = "transport: Error while dialing dial tcp :10001: socket: too many open files" ... connection error: desc = "transport: Error while dialing dial tcp :10001: socket: too many open files" FAIL exit status 1当你的应用场景是存在高频次同时生成/调用 ClientConn 时,可能会导致系统的文件句柄占用过多。这种情况下你可以变更应用程序生成/调用 ClientConn 的模式,又或是池化它,这块可以参考 grpc-go-pool 项目。
8. 客户端请求失败后会默认重试吗?会不断地进行重试,直到上下文取消。而重试时间方面采用 backoff 算法作为的重连机制,默认的最大重试时间间隔是 120s。
9. 为什么要用 HTTP/2 作为传输协议?许多客户端要通过 HTTP 代理来访问网络,gRPC 全部用 HTTP/2 实现,等到代理开始支持 HTTP/2 就能透明转发 gRPC 的数据。不光如此,负责负载均衡、访问控制等等的反向代理都能无缝兼容 gRPC,比起自己设计 wire protocol 的 Thrift,这样做科学不少。@ctiller @滕亦飞
10. 在 Kubernetes 中 gRPC 负载均衡有问题?gRPC 的 RPC 协议是基于 HTTP/2 标准实现的,HTTP/2 的一大特性就是不需要像 HTTP/1.1 一样,每次发出请求都要重新建立一个新连接,而是会复用原有的连接。