☰
go-grpc服务端调用链
2026/10/14 9:16:13 网站建设 项目流程

一、goroutine 模型总览(先看图再读码)

s.Serve(lis) ← 主 goroutine:Accept 循环(server.go:906) │ ├─ go handleRawConn(conn) ← 每条 TCP 连接 1 个(server.go:991) │ │ │ └─ newHTTP2Server → st.HandleStreams(...) ← 连接读循环 │ │ │ └─ go handleStream(st, stream) ← 每条 RPC 1 个(server.go:1096) │ │ │ └─ processRPC → 拦截链 → 你的 SayHello ← 业务跑在这里 │ └─ (可选) serverWorkerChannel ← numServerWorkers>0 时,固定 worker 池 替代 per-stream goroutine,防 goroutine 爆炸

三层职责:Accept(不阻塞)→ 连接读循环(解析帧)→ 流处理(业务逻辑)。这就是经典的goroutine-per-connection + goroutine-per-request模型。

二、逐站精读

站① Serve:带退避的 Accept 循环(server.go:906-996)

func (s *Server) Serve(lis net.Listener) error { ... for { rawConn, err := lis.Accept() if err != nil { if ne, ok := err.(interface{ Temporary() bool }); ok && ne.Temporary() { // 临时错误:5ms 起步的指数退避,封顶 1s(server.go:956-961) // —— Accept 也怕打满 CPU,和客户端重连退避同一思路 ...continue } return err } // 每条连接立刻丢给新 goroutine,绝不阻塞 Accept 循环(server.go:985-994) s.serveWG.Add(1) go func() { s.handleRawConn(lis.Addr().String(), rawConn) s.serveWG.Done() }() } }

细节品味:serveWG(WaitGroup)让GracefulStop能等所有连接处理完——优雅退出在第一行就埋了伏笔。

站② handleRawConn → 建传输层(server.go:1000+)

func (s *Server) handleRawConn(lisAddr string, rawConn net.Conn) { if s.quit.HasFired() { rawConn.Close(); return } // 已停机:直接掐新连接 rawConn.SetDeadline(time.Now().Add(s.opts.connectionTimeout)) // 握手超时 // 完成 TLS 握手(若配了)→ newHTTP2Server 建立 HTTP/2 传输 // 失败 → 关连接走人(错误连接不值得占资源) ... }

newHTTP2Server(internal/transport/http2_server.go)做三件事:HTTP/2 握手交换 SETTINGS、分配连接级流控窗口、启动 reader 循环和 loopyWriter(与客户端对称,11 篇)。

站③ HandleStreams:帧 → 流 的分发

http2_server.go的读循环逐帧解析(HEADERS 帧开流、DATA 帧挂到对应流),每发现新流就回调:

// server.go:1090-1100(节选) st.HandleStreams(ctx, func(stream *transport.ServerStream) { s.handlersWG.Add(1) streamQuota.acquire() // 限流:并发流数受 serverOption 约束 f := func() { defer streamQuota.release() defer s.handlersWG.Done() s.handleStream(st, stream) // ← 每条流最终进这里 } if s.opts.numServerWorkers > 0 { select { case s.serverWorkerChannel <- f: // worker 池模式 return default: // 池满 → 回退 goroutine 模式 } } go f() })

两个生产级细节:

  • streamQuota:单连接并发流上限,防止一条恶意连接拖死服务端;
  • worker 池可选:grpc.ServerOption设numServerWorkers,把 per-stream goroutine 换成固定池——高 QPS 场景减少调度开销。

站④ handleStream:方法路由(server.go:1537-1597)

这段是"HTTP/2 世界的路由器":

func (s *Server) handleStream(t transport.ServerTransport, stream *transport.ServerStream) { ... sm := stream.Method() // "/helloworld.Greeter/SayHello" service, method := sm拆包 // "helloworld.Greeter" / "SayHello" // (server.go:1562-1573,CutPrefix+LastIndex) if srv, known := s.services[service]; known { if sd, ok := srv.streams[method]; ok { // ② 查该服务的方法表 s.processRPC(ctx, stream, srv, sd, ti) // ③ 命中 → 处理 return } } // 未命中 → WriteStatus(codes.Unimplemented) ← 你调错方法名时见到的错误 }

s.services这张 map 就是RegisterGreeterServer注册时填的(key 来自 02 篇讲的ServiceDesc)——02 篇的生成物与 09 篇的路由此刻接上了。

站⑤ processRPC:拦截链 + 业务(server.go:1292+)

v1.85 把老版本的processUnaryRPC/processStreamingRPC合并统一了:

func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, info *serviceInfo, sd *StreamDesc, trInfo *traceInfo) (err error) { // ① stats/channelz 埋点(Begin 事件) // ② 组装 serverStream:解析器 parser、codec(按 Content-Subtype 选)、 // 消息大小上限、trace —— 这是传给你 handler 的"流句柄"本体 ss := &serverStream{ctx: ctx, s: stream, codec: s.getCodec(...), ...} // ③ 统一的 defer:stats.End/channelz 计数/trace 收尾 // (server.go:1319-1361 注释还解释了为何合并成一个 defer:省栈帧) // ④ 穿过服务端拦截链(04 篇注册的 ChainUnaryInterceptor 在此生效) // 一元: chainedInterceptors → 你的 SayHello // 流式: chainedStreamInterceptors → 你的 RouteChat 等 // ⑤ handler 返回 → 把结果/status 装进响应帧与 trailer → 流关闭 }

注意s.getCodec(stream.ContentSubtype())——服务端按请求头里的编码子类型选编解码器(encoding 注册表的运行时查询),所以客户端换编码不用改服务端。

站⑥ 响应回程

你的SayHello返回(*HelloReply, error)之后:

reply ──► codec.Marshal ──► 5字节前缀+体 ──► transport 写队列 error ──► status.Status ──► trailer(grpc-status/grpc-message) ──► loopyWriter 打帧(DATA + HEADERS-trailer) ──► 客户端 08 篇站⑦

正常路径 grpc-status=0;handler panic 没有 recover 拦截器的话,这条流挂掉但 server 进程不挂(grpc-go 自己在 processRPC 外层兜了 panic→Internal 错误的边界——但你的日志会丢,所以 04 篇 recovery 拦截器仍是标配)。

三、Stop vs GracefulStop(server.go:1676/1683)

Stop()

GracefulStop()

新 RPC

立即拒绝

拒绝(发 GOAWAY 通知客户端"我要退役")

存量 RPC

立刻断,客户端收到 Unavailable

等全部完成(或 ctx 兜底)

连接

关

先 GOAWAY 让客户端平滑迁到别的实例

用途

致命错误自保

发版/缩容(06 篇清单第 4 条)

源码线索:s.quit(Event)、serveWG/handlersWG两个 WaitGroup 分层等待——连接层和流层的生命周期是分开管的。分层等待——连接层和流层的生命周期是分开管的。

五、本篇小结

站点

文件:行

干什么

Serve

server.go:906

Accept 循环 + 临时错误指数退避

handleRawConn

server.go:1000

TLS/HTTP2 握手,建传输

HandleStreams 回调

server.go:1090

per-stream goroutine 或 worker 池

handleStream

server.go:1537

"/服务/方法" 路由到 ServiceDesc

processRPC

server.go:1292

组 serverStream → 拦截链 → 业务

回程

—

codec + status → loopyWriter → 帧

  • 服务端 =Accept 循环 + 每连接读循环 + 每流 goroutine三层;
  • 老教程的 processUnaryRPC 已统一为 processRPC(版本差注意);
  • register 的 ServiceDesc就是路由表,生成代码与运行时在此闭环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询