TL;DR
- 实时行情推送的难点不是“能发出去”,而是同时满足毫秒级端到端延迟、海量长连接、协议可演进与慢客户端不拖垮系统(反压)。
- gRPC Streaming 的核心价值在于:借助 HTTP/2 多路复用(同一 TCP 连接上并发多条逻辑流)+ HPACK 头压缩 + 协议级流控,把“连接管理/反压/帧化”这些底层工程难题交给成熟实现。
- 相比 WebSocket(只给你“帧”)、原生 TCP(全得自己造),gRPC 还能用 Protobuf 获得紧凑载荷与强类型契约,接口演进成本更低。
- 落地时最大坑通常在:双向流的并发收发模型、生命周期/资源回收、心跳与 Keepalive 策略、以及“长连接负载均衡怎么做”。
1. 先对齐问题:行情推送为什么容易“看起来能跑”,但一上量就崩
典型场景:中心化行情源(撮合/行情聚合)需要把 Ticker、OrderBook 深度、逐笔成交等数据,推送给分布在全球的数千到数万个客户端。
你会遇到四类硬约束:
- 低延迟 + 低抖动:行情抖一下,策略就可能滑点;
- 长连接规模:文件描述符、内存、线程/协程调度都是上限;
- 协议效率:JSON/HTTP 头部开销在高频下会“磨死”CPU 和带宽;
- 慢消费者问题:某些客户端处理不过来时,不能让服务器无限堆积消息。
2. 关键原理拆解:gRPC Streaming 为什么更适合“高频推送”
HTTP/2 多路复用:一条 TCP 顶多条逻辑 Stream
HTTP/2 在应用层引入二进制分帧,每个 RPC(包括流式 RPC)对应一个 Stream,不同 Stream 的帧可以交错传输、在对端按 Stream ID 重组。
对行情网关来说,这意味着:同一连接内可以并发接收订阅指令、推送行情数据,而不会被 HTTP/1.1 的队头阻塞拖累。
协议级流控:把“反压”从业务代码里抽出来
HTTP/2 的 Flow Control 在 Stream/Connection 两层提供窗口机制:接收方窗口耗尽,发送方就必须降速。
这对行情推送很关键——当某个 Web 端或低配机器变慢时,系统不会无上限堆积内存把自己撑爆。
Protobuf:更小的 payload + 更可控的演进
对比 JSON,Protobuf 的二进制编码通常能把载荷缩到 1/5~1/10,序列化/反序列化 CPU 开销也更低。
更重要的是 .proto 作为强类型契约,让字段新增/兼容策略更清晰,避免“线上才发现解析炸了”。
3. 工程关键要点/坑:真正会踩的 6 个点
- 双向流必须“读写分离”:服务端通常需要一个 goroutine/协程负责 Recv(订阅/退订),另一个负责 Send(行情/心跳)。单协程阻塞读会导致写永远发不出去。
- 生命周期与资源回收:一定要监听
stream.Context().Done()(或等价机制)来停止推送 goroutine,否则很容易连接断了但后台还在发,最后内存/CPU 泄漏。
- 订阅状态的并发安全:订阅表(map/set)会被读写并发访问,必须加锁或用并发容器;别让“偶发崩溃”变成线上彩蛋。
- Keepalive vs 应用层心跳:gRPC/HTTP2 的 PING 更偏“链路存活”;应用层心跳能验证业务处理链路是否卡死。实际落地通常二者配合:Keepalive 探死连接,应用心跳判“假活”。
- 长连接负载均衡:传统 L7 轮询转发容易打断/不友好;常见实践是客户端负载均衡(客户端从注册中心拿节点列表自行选择并重连),或用理解 gRPC 的代理(如 Envoy)做更稳健的连接迁移。
- 别把极致性能优化太早做:绝大多数团队用 Protobuf + 合理的推送节流/批量合并就能跑到很高;只有在纳秒级极限场景,才值得考虑 SBE/手写零拷贝协议这种“维护成本炸裂”的方案。
4. 适用场景:什么时候该优先考虑 gRPC Streaming
- 你在做行情网关、风控推送、撮合周边服务等高频消息分发;
- 客户端不止 Web(还有交易机器人/桌面端/内部服务),希望多语言一致的 SDK 体验;
- 你需要协议“可进化”,而不是每次加字段都靠约定与灰度踩雷;
- 你希望反压/流控、连接管理尽量标准化,减少自研协议栈负担。