资讯动态

HTTP2传输层与流控

发布时间:2026/10/17 2:39:37 来源:尧图企业网站定制
一、为什么 gRPC 必须用 HTTP/2HTTP/1.1 的痛HTTP/2 的解药一连接一请求(队头阻塞)Stream 多路复用:一条 TCP 跑千百条并发 RPC文本协议开销二进制帧(Frame)分帧头部重复冗长HPACK头压缩(字典哈夫曼)无服务端主动推送语义双向流天然匹配 gRPC 四种模式二、目标检验③一次一元调用的 HTTP/2 报文图协议依据官方 PROTOCOL-HTTP2.md源码走读时以它对照http_util.go的常量。连接建立(一次)TCP 连接 ◄──────────────────────────────────────────────► Client Server │ magic PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n ──► │ 连接前言 │ ◄─────── SETTINGS 帧(初始窗口/MaxFrameSize) ─ │ 双方交换参数 │ ──────── SETTINGS(ACK) ────────────────────► │请求方向客户端 → 服务端── HEADERS 帧 (Stream ID1, END_HEADERS) ──────────────────────────► 伪头部 元数据(HPACK 压缩后): :method POST :scheme http (TLS 时是 https) :path /helloworld.Greeter/SayHello ← 02 篇讲的全名! :authority localhost:50051 content-type application/grpc ← 必须,grpc 的身份证 user-agent grpc-go/1.85.0 grpc-timeout 3000m ← 05 篇的超时预算(3 秒) authorization bearer xxx ← 06 篇的 metadata ── DATA 帧 (Stream ID1) ─────────────────────────────────────────► ┌─────────┬─────────────┬───────────────────────────┐ │ 压缩标志 │ 消息长度 │ Protobuf 编码的消息体 │ │ 1 byte │ 4 bytes │ N bytes │ │ (0/1) │ uint32 大端 │ (压缩时先 compress 再计长度) │ └─────────┴─────────────┴───────────────────────────┘ ★ gRPC 自定义的长度前缀帧——protobuf 不自报长度,靠这 5 字节分界 END_STREAM 标志:一元调用随 DATA 置位;流式调用由客户端发空 DATA 时置位 ### 响应方向服务端 → 客户端 ◄── HEADERS 帧 (Stream ID1) ──────────────────────────────────── :status 200 (HTTP 层 OK;gRPC 错误不看这个!) content-type application/grpc grpc-encoding gzip (若压缩) ◄── DATA 帧 (Stream ID1) ─────────────────────────────────────── 同样的 5 字节前缀 响应消息体 ◄── HEADERS 帧(trailer, END_STREAM) ───────────────────────────── grpc-status 0 ← gRPC 真正的状态码在这! grpc-message xxx (失败时的描述) ★ 这就是 08 篇多收一次 RecvMsg 等 trailer的物理对应物一图记住请求 HEADERS DATA响应 HEADERS DATA trailer(HEADERS)gRPC 状态在 trailer消息靠 5 字节前缀分界。流式调用只是 DATA 帧可以出现 N 次而已。三、grpc-go 的传输层实现自研非 net/httpinternal/transport/的核心文件分工文件职责transport.go接口定义ClientTransport(:714)/ServerTransport(:756)帧类型、错误类型http2_client.go客户端建连握手、reader goroutine、keepalive PING 定时器、GOAWAY 处理http2_server.go服务端连接握手、逐帧解析开流、流数配额、GOAWAY(MaxConnectionAge 触发)controlbuf.goloopyWriter(:522)每连接唯一的写 goroutine 优先级发送队列flowcontrol.go流控窗口账本trInFlow/outFlow/outStreamContextbdp_estimator.goBDP 带宽×延迟积估计动态调大窗口吃满长肥管道http_util.go帧读写工具、头部编码、grpc-timeout/状态解析用的是golang.org/x/net/http2的Framer 和 HPACK编码层复用但连接管理、流调度、应用层逻辑全部自研。原因net/http 的抽象无法提供 gRPC 需要的精细流控、GOAWAY 语义、keepalive 策略与连接级调优。例外Server.ServeHTTP(server.go:1141) 允许把 gRPC 挂到 net/http 服务器上与他人共享端口注释明确标注 EXPERIMENTAL 且性能/特性受限。四、单写者模型loopyWritercontrolbuf.go:522应用侧(任意多条流,任意多 goroutine) │ SendMsg → 写入 controlBuf(并发安全队列) ▼ ┌─────────── controlBuf(按优先级组织) ───────────┐ │ 高优先级:流控帧/SETTINGS ACK/PING 回应 │ │ 中优先级:HEADERS │ │ 普通优先级:各流的 DATA │ └───────────────────────┬────────────────────────┘ │ 唯一消费者 loopyWriter.run() (controlbuf.go:595,每连接 1 个 goroutine) loop { 取当前可发的最高优先级项 检查目标流的 HTTP/2 发送窗口余额 ├─ 够: 聚合成帧(可合并多个小消息) → framer 写出 └─ 不够: 记账挂起,先发别的;收到 WINDOW_UPDATE 再唤醒 处理 keepalive PING 到期(客户端) }设计收益无锁写 socket——多流并发不再竞争连接优先级调度——流控记账、PING 回应永远插队在大块数据之前避免队头的大文件饿死控制帧小消息聚合——多次 SendMsg 的数据可合进一个帧减少系统调用。五、流控HTTP/2 窗口 BDP两级窗口连接级窗口(整条 TCP 的在途字节上限) └── 流级窗口(单条 RPC 的在途字节上限,默认初始 64KB,可配) 发送方视角: 每发 DATA,窗口余额减少 接收方视角: 应用读走数据后发 WINDOW_UPDATE 帧还款背压传导链服务端 handler 处理慢 → 不读 RecvMsg → 不发 WINDOW_UPDATE → 客户端 loopyWriter 窗口耗尽挂起 → 客户端SendMsg开始阻塞 →上游收不到推送压力自然减速。整条链路无需任何额外代码这就是 03 篇流式铁律背后的机制。BDP 估计器bdp_estimator.go高延迟大带宽链路跨机房默认 64KB 窗口严重吃不满管道。BDP 估算器通过给 ACK 打时间戳测 RTT 与吞吐动态向对端申请放大窗口WINDOW_UPDATE发大增量。这解释了一个现象抓包能看到窗口越涨越大。六、keepalive 在传输层的样子客户端http2_client.go定时器到期 → 无活动则发 PING(带数据) → 等 PONGTimeout内无回应对应keepalive.ClientParameters.Timeout判死连接服务端http2_server.go收到 PING → 立即 PONG同时执法——两次 PING 间隔 MinTime且无豁免 → 发 GOAWAY 断连MaxConnectionAge到期 → 服务端主动 GOAWAY → 客户端收到后把该 transport 标记退役重连会走 balancer 重新 pick——滚动重启的优雅感来源。

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

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

免费获取报价 →
↑