在 newClientStream 方法中,我们通过 getTransport 方法获取了 Transport 层中抽象出来的 ClientTransport 和 ServerTransport,实际上就是获取一个连接给后续 RPC 调用传输使用。
四、关闭连接 // conn.Close() func (cc *ClientConn) Close() error { defer cc.cancel() ... cc.csMgr.updateState(connectivity.Shutdown) ... cc.blockingpicker.close() if rWrapper != nil { rWrapper.close() } if bWrapper != nil { bWrapper.close() } for ac := range conns { ac.tearDown(ErrClientConnClosing) } if channelz.IsOn() { ... channelz.AddTraceEvent(cc.channelzID, ted) channelz.RemoveEntry(cc.channelzID) } return nil }该方法会取消 ClientConn 上下文,同时关闭所有底层传输。涉及如下:
Context Cancel
清空并关闭客户端连接
清空并关闭解析器连接
清空并关闭负载均衡连接
添加跟踪引用
移除当前通道信息
Q&A 1. gRPC Metadata 是通过什么传输? 2. 调用 grpc.Dial 会真正的去连接服务端吗?会,但是是异步连接的,连接状态为正在连接。但如果你设置了 grpc.WithBlock 选项,就会阻塞等待(等待握手成功)。另外你需要注意,当未设置 grpc.WithBlock 时,ctx 超时控制对其无任何效果。
3. 调用 ClientConn 不 Close 会导致泄露吗?会,除非你的客户端不是常驻进程,那么在应用结束时会被动地回收资源。但如果是常驻进程,你又真的忘记执行 Close 语句,会造成的泄露。如下图:
3.1. 客户端
3.2. 服务端
3.3. TCP
4. 不控制超时调用的话,会出现什么问题?短时间内不会出现问题,但是会不断积蓄泄露,积蓄到最后当然就是服务无法提供响应了。如下图:
5. 为什么默认的拦截器不可以传多个? func chainUnaryClientInterceptors(cc *ClientConn) { interceptors := cc.dopts.chainUnaryInts if cc.dopts.unaryInt != nil { interceptors = append([]UnaryClientInterceptor{cc.dopts.unaryInt}, interceptors...) } var chainedInt UnaryClientInterceptor if len(interceptors) == 0 { chainedInt = nil } else if len(interceptors) == 1 { chainedInt = interceptors[0] } else { chainedInt = func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error { return interceptors[0](ctx, method, req, reply, cc, getChainUnaryInvoker(interceptors, 0, invoker), opts...) } } cc.dopts.unaryInt = chainedInt }当存在多个拦截器时,取的就是第一个拦截器。因此结论是允许传多个,但并没有用。
6. 真的需要用到多个拦截器的话,怎么办?可以使用 go-grpc-middleware 提供的 grpc.UnaryInterceptor 和 grpc.StreamInterceptor 链式方法,方便快捷省心。