资讯动态

grpc客户端调用链

发布时间:2026/10/13 3:14:35 来源:尧图企业网站定制
一、调用链全景图生成的存根 SayHello (helloworld_grpc.pb.go) │ cc.Invoke(ctx, /helloworld.Greeter/SayHello, in, out) ▼ ① ClientConn.Invoke call.go:33 │ 有 unaryInt 拦截器?先穿过拦截链(04 篇) ▼ ② invoke call.go:69 │ 一元也是流:unaryStreamDesc ▼ ③ newClientStream stream.go:302 │ 合并 callOptions/拿 method config(重试/超时策略) ▼ ④ newClientStreamWithParams stream.go:398 │ ★ pickerWrapper.pick ──► balancer 给出 SubConn ──► ClientTransport │ ★ 失败时决定:等(wait_for_ready)/失败/可重试则走 retry 逻辑 ▼ ⑤ cs.sendMsg ──► transport.ClientStream.SendMsg │ codec.Marshal(proto 序列化) 可选压缩 5字节长度前缀 ▼ ⑥ loopyWriter(controlbuf.go:522) ←—— 每连接唯一的写 goroutine │ 把消息装进 HTTP/2 DATA 帧,受 flowcontrol 窗口约束 ▼ ⑦ TCP 字节流 ──► reader goroutine 收响应 │ HEADERS 帧 → 元数据;DATA 帧 → 消息体;trailer → status ▼ ⑧ cs.RecvMsg(reply) ──► codec.Unmarshal ──► return (out, err) 回到你的代码二、逐站精读站① Invoke拦截器的入口call.go:33-42func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply any, opts ...CallOption) error { opts combine(cc.dopts.callOptions, opts) // 合并通道级本次调用的选项 if cc.dopts.unaryInt ! nil { // 有客户端一元拦截器? return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...) // 注意 ↑↑↑↑↑ ↑↑↑↑↑ // invoke(真正干活的)作为 invoker 参数递给拦截器 —— 04 篇同心圆的源码实锤 } return invoke(ctx, method, args, reply, cc, opts...) }这就是为什么 说客户端拦截器是生成代码→Invoke→你的拦截器→真实调用的同心圆——invoke被当作函数值传进拦截器调用与否完全由拦截器决定。站② invoke一元的流式本质call.go:67-92var unaryStreamDesc StreamDesc{ServerStreams: false, ClientStreams: false} // :67 func invoke(ctx context.Context, method string, req, reply any, cc *ClientConn, opts ...CallOption) error { cs, err : newClientStream(ctx, unaryStreamDesc, cc, method, opts...) // 建流 ... cs.SendMsg(req) // 发唯一一条请求 cs.CloseSend() // 半关闭:告诉服务端我没有更多了(xDS 拦截器需要) cs.RecvMsg(reply) // 收唯一一条响应 cs.RecvMsg(reply) // 再收一次拿 trailer(io.EOF 正常结束) ... }关键认知一元调用 严格发1收1的流。grpc-go 没有为它开小灶——03 篇的论断在这里拿到了源码证据。站③④ newClientStream一次调用最复杂的 100 行stream.go:302/398这站干四件事取配置从 service config 拿该方法的MethodConfig重试策略、超时、消息上限——服务端可以下发挑连接全库核心picker_wrapper.gopickerWrapper.pick(ctx) │ ├─ balancer.Picker.Pick(info) ← 当前负载均衡策略给出 SubConn │ ├─ 返回 SubConn → 取它的 ClientTransport,继续 │ ├─ ErrNoSubConnAvailable → 阻塞等 balancer 更新 Picker(连接还在建) │ └─ 返回错误(如 TRANSIENT_FAILURE) │ ├─ fail-fast(默认):立即把错误抛给调用方 │ └─ WaitForReady(true):继续等,直到 READY 或 ctx 超时 │ └─ 拿到 transport 后还有一个环路:transport 已断? 重新 pick(最多重试若干次,配合 transparent retry)判定可重试请求是否幂等、错误码是否在 retryPolicy 白名单满足则失败后自动换连接重发attempt循环就在这条链上组装 clientStream把 transport、压缩器、编解码器、stats/binlog 钩子、ctx 打包成一个对象返回。站⑤ SendMsg消息 → 字节clientStream.SendMsgstream.go最终落到 transportSendMsg(m) 1. codec.Marshal(m) proto 默认编解码 → []byte(encoding/codec.go 注册表) 2. 可选 compressor gzip/zstd(压缩器也是注册表) 3. 组帧: 5 字节前缀 体 [压缩标志1B][长度4B][消息体] 4. 写入 transport 的发送队列 → 交给 loopyWriter,自己立即返回(异步!)注意SendMsg 返回≠字节已上网只是进了发送队列。真正的阻塞点在 HTTP/2 流控窗口11 篇。站⑥ loopyWriter每连接唯一的写者controlbuf.go:522, run:595应用 goroutine(多条流) loopyWriter goroutine(每连接1个) │ 多路写入 │ ▼ ▼ controlBuf(发送队列,带优先级) ──► run() 循环: - headers/data 等待发送 ① 取最高优先级待发项 - 流控信息优先 ② 检查该流 HTTP/2 窗口够不够 ③ 够 → 打帧写出;不够 → 记账,先发别的 ④ 处理 PING/SETTINGS/窗口更新等控制帧单写者模型是 grpc-go 传输层的灵魂所有流共享一个写 goroutine避免多 goroutine 写 socket 的锁竞争与帧交错——07 篇顺 goroutine 读的技巧在这里最有效。站⑦⑧ 收响应与解包reader goroutine每连接 1 个http2_client.go用http2.Framer逐帧解析HEADERS帧 → 响应 metadatastream.Header()拿到的就是它DATA帧 → 去掉 5 字节前缀 → 解压 →codec.Unmarshal→RecvMsg(m)返回HEADERS(trailer) 帧 →grpc-status/grpc-message→ 流终态一元路径收完第一条消息后那次多余的 RecvMsg站②正是在等 trailer拿到io.EOF即codes.OK。错误在客户端的还原路径trailer 的grpc-status: 7→status.Error(codes.PermissionDenied, ...)→ 06 篇你手里的那个 err。三、超时与元数据如何上网ctx的 deadline 在建流时被encoding/timeout.go编码成grpc-timeout: 3000m放进 HEADERS——这就是跨服务超时预算的物理载体metadata.NewOutgoingContext塞的 md 同样拼进 HEADERS与服务端FromIncomingContext首尾呼应ctx 取消 → 关闭对应 HTTP/2 streamRST_STREAM对端立刻收到Canceled四本篇小结站点文件:行干什么Invokecall.go:33合并选项,穿过客户端拦截器invokecall.go:69用退化的流实现一元newClientStreamstream.go:302Picker 选连接 重试判定 组流SendMsgstream.gocodec 序列化 5 字节前缀入队loopyWritercontrolbuf.go:522单写者打 HTTP/2 帧,受流控约束reader RecvMsghttp2_client.go收帧→解包→Unmarshal→还原 status一元即流unaryStreamDesc证明四种模式同源call.go:67阻塞点不在 Send 而在 pick 与流控窗口拦截器拿到invoke函数值同心圆结构成立。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑