/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
C++ RPC 技术原理:从一次调用到百万连接的工程实现
这不是一篇 API 使用教程,而是把「一次远程调用到底发生了什么」拆开到字节与状态机级别。
文中所有设计取舍都来自真实工程约束:字节序、粘包、背压、超时、幂等、内存与锁。
范围说明:标题里的「百万连接」是工程量级目标,不是本文给出的实测结果;连接数上限由
内存、文件描述符上限、内核参数与每连接状态决定,必须实测而不是引用。
一、RPC 的本质
一次本地调用 ret = svc->echo(req) 在 CPU 看来只有三步:把参数放进寄存器/栈、跳转、把返回值拿回来。RPC 做的,是把这三步替换成:
调用方: 序列化参数 → 组帧 → 写入连接 → 等待响应 → 反序列化返回值
被调方: 读连接 → 解帧 → 反序列化参数 → 执行 → 序列化返回值 → 回写
被调方连接(字节流)调用方被调方连接(字节流)调用方#mermaid-svg-SDQJHbGX9ffXSNEU{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SDQJHbGX9ffXSNEU .error-icon{fill:#552222;}#mermaid-svg-SDQJHbGX9ffXSNEU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SDQJHbGX9ffXSNEU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SDQJHbGX9ffXSNEU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SDQJHbGX9ffXSNEU .marker.cross{stroke:#333333;}#mermaid-svg-SDQJHbGX9ffXSNEU svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SDQJHbGX9ffXSNEU p{margin:0;}#mermaid-svg-SDQJHbGX9ffXSNEU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SDQJHbGX9ffXSNEU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SDQJHbGX9ffXSNEU .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-SDQJHbGX9ffXSNEU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-SDQJHbGX9ffXSNEU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-SDQJHbGX9ffXSNEU .sequenceNumber{fill:white;}#mermaid-svg-SDQJHbGX9ffXSNEU #sequencenumber{fill:#333;}#mermaid-svg-SDQJHbGX9ffXSNEU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-SDQJHbGX9ffXSNEU .messageText{fill:#333;stroke:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SDQJHbGX9ffXSNEU .labelText,#mermaid-svg-SDQJHbGX9ffXSNEU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .loopText,#mermaid-svg-SDQJHbGX9ffXSNEU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SDQJHbGX9ffXSNEU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-SDQJHbGX9ffXSNEU .noteText,#mermaid-svg-SDQJHbGX9ffXSNEU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-SDQJHbGX9ffXSNEU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SDQJHbGX9ffXSNEU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SDQJHbGX9ffXSNEU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SDQJHbGX9ffXSNEU .actorPopupMenu{position:absolute;}#mermaid-svg-SDQJHbGX9ffXSNEU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-SDQJHbGX9ffXSNEU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SDQJHbGX9ffXSNEU .actor-man circle,#mermaid-svg-SDQJHbGX9ffXSNEU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-SDQJHbGX9ffXSNEU :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}序列化参数1组帧并写入2字节流(可能粘包/半包)3增量解帧 + 反序列化4执行 handler5序列化返回值并回写6响应帧7按 stream_id 匹配并反序列化8
复杂度不在"传数据",而在把不可靠、无序、会阻塞、会分区、会重启的网络,包装成看起来同步可靠的函数调用。所有 RPC 框架的差异,都体现在这句话的括号里。
一个成熟的 C++ RPC 框架至少要同时解决六类问题:
#mermaid-svg-V3t4u7oszSDZxV6T{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V3t4u7oszSDZxV6T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V3t4u7oszSDZxV6T .error-icon{fill:#552222;}#mermaid-svg-V3t4u7oszSDZxV6T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V3t4u7oszSDZxV6T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V3t4u7oszSDZxV6T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V3t4u7oszSDZxV6T .marker.cross{stroke:#333333;}#mermaid-svg-V3t4u7oszSDZxV6T svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V3t4u7oszSDZxV6T p{margin:0;}#mermaid-svg-V3t4u7oszSDZxV6T .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-V3t4u7oszSDZxV6T .cluster-label text{fill:#333;}#mermaid-svg-V3t4u7oszSDZxV6T .cluster-label span{color:#333;}#mermaid-svg-V3t4u7oszSDZxV6T .cluster-label span p{background-color:transparent;}#mermaid-svg-V3t4u7oszSDZxV6T .label text,#mermaid-svg-V3t4u7oszSDZxV6T span{fill:#333;color:#333;}#mermaid-svg-V3t4u7oszSDZxV6T .node rect,#mermaid-svg-V3t4u7oszSDZxV6T .node circle,#mermaid-svg-V3t4u7oszSDZxV6T .node ellipse,#mermaid-svg-V3t4u7oszSDZxV6T .node polygon,#mermaid-svg-V3t4u7oszSDZxV6T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V3t4u7oszSDZxV6T .rough-node .label text,#mermaid-svg-V3t4u7oszSDZxV6T .node .label text,#mermaid-svg-V3t4u7oszSDZxV6T .image-shape .label,#mermaid-svg-V3t4u7oszSDZxV6T .icon-shape .label{text-anchor:middle;}#mermaid-svg-V3t4u7oszSDZxV6T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V3t4u7oszSDZxV6T .rough-node .label,#mermaid-svg-V3t4u7oszSDZxV6T .node .label,#mermaid-svg-V3t4u7oszSDZxV6T .image-shape .label,#mermaid-svg-V3t4u7oszSDZxV6T .icon-shape .label{text-align:center;}#mermaid-svg-V3t4u7oszSDZxV6T .node.clickable{cursor:pointer;}#mermaid-svg-V3t4u7oszSDZxV6T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V3t4u7oszSDZxV6T .arrowheadPath{fill:#333333;}#mermaid-svg-V3t4u7oszSDZxV6T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V3t4u7oszSDZxV6T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V3t4u7oszSDZxV6T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V3t4u7oszSDZxV6T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V3t4u7oszSDZxV6T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V3t4u7oszSDZxV6T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V3t4u7oszSDZxV6T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V3t4u7oszSDZxV6T .cluster text{fill:#333;}#mermaid-svg-V3t4u7oszSDZxV6T .cluster span{color:#333;}#mermaid-svg-V3t4u7oszSDZxV6T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V3t4u7oszSDZxV6T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V3t4u7oszSDZxV6T rect.text{fill:none;stroke-width:0;}#mermaid-svg-V3t4u7oszSDZxV6T .icon-shape,#mermaid-svg-V3t4u7oszSDZxV6T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V3t4u7oszSDZxV6T .icon-shape p,#mermaid-svg-V3t4u7oszSDZxV6T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V3t4u7oszSDZxV6T .icon-shape .label rect,#mermaid-svg-V3t4u7oszSDZxV6T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V3t4u7oszSDZxV6T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V3t4u7oszSDZxV6T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V3t4u7oszSDZxV6T :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
业务代码(stub / 反射调用)
序列化(binary / JSON / protobuf)
帧层(长度 / 类型 / stream_id / flags)
传输(inproc / epoll ET / IOCP)
网络(TCP)
对端传输
解帧与分发
反序列化 + handler
响应沿原路返回
下面逐层拆。
二、序列化:从 IDL 到反射
2.1 两条路线
- IDL 路线(gRPC/Thrift):写 .proto/.thrift,编译器生成 stub 与编解码。优点是跨语言、契约清晰;代价是工具链与"改一个字段要重新生成"的仪式感。
- 反射路线(本类框架):用宏/属性在 C++ 类型上标注字段,运行期或编译期生成元数据,序列化器遍历字段。优点是"加字段即生效、无代码生成";代价是编译期开销与模板膨胀。
C++ 里做反射序列化,核心是把"字段列表"变成一个可遍历的元组:
struct user {
std::uint64_t id;
std::string name;
int age;
};
RTTI_DEFINE(user, id, name, age); // 生成字段名、偏移、类型信息
遍历元组即可实现 JSON/XML/binary 多格式:
template <typename T, typename F>
void for_each_field(T& v, F&& f) {
rtti::visit_fields<T>([&](auto& field) { f(field.name(), field.value(v)); });
}
2.2 二进制格式的关键决策
- 字节序:协议里写死小端(或显式标注),禁止"跟随主机"。跨端互通是第一天就要满足的正确性要求,不是"以后再说"。
- 变长整数:小整数占 1 字节(varint),大整数按需扩展;代价是解码分支。
- 长度前缀 vs 分隔符:需要自定界的记录(帧、字符串、嵌套消息)用长度前缀;定宽字段直接按宽度读,不需要额外长度。文本格式才用分隔符。
- 字段编号而非字段名:跨版本兼容靠编号 + 未知字段跳过。
- 零拷贝:解码时让 string/bytes 直接指向接收缓冲区(借用语义),但必须把"缓冲区生命周期"写进类型系统或文档,否则就是悬垂指针批发。
一个常见陷阱:空消息。proto3 中所有字段都是默认值时,合法的 wire 编码就是 0 字节。如果框架用"输出为空"判断编码失败,就会把合法消息误判为错误。判断编码成败必须用错误码,不能用输出长度。
三、线协议:帧、复用、协商
3.1 帧结构
一个多路复用的二进制协议,帧头至少要有:长度、类型、流 ID、标志位。
// 线格式必须显式序列化。不要用 struct 的内存布局当协议:
// 下面的结构在 x64 MSVC 上 sizeof == 12(stream_id 被对齐到 offset 8),
// 不是字段宽度之和的 10;多平台/多编译器下还会变。
// 反例(错误示范):static_assert(sizeof(frame_header) == 10);
struct frame_header {
std::uint32_t length; // 含头部的总长度
std::uint8_t type; // REQUEST / RESPONSE / PUSH / PING / SETTINGS / GOAWAY …
std::uint8_t flags; // 压缩、可靠、有序扩展等
std::uint32_t stream_id; // 多路复用标识
};
// 正确做法:按字节写死偏移,编译期断言字段宽度,而不是断言结构体大小。
inline void encode_header(std::byte* out, const frame_header& h) noexcept {
write_le32(out + 0, h.length);
out[4] = static_cast<std::byte>(h.type);
out[5] = static_cast<std::byte>(h.flags);
write_le32(out + 6, h.stream_id); // 协议偏移 6,不是结构体偏移 8
}
帧头字段固定后,同一条连接上多个调用以 stream_id 交错传输:
服务端客户端服务端客户端#mermaid-svg-cTqFX1zYNPy0Zsvb{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cTqFX1zYNPy0Zsvb .error-icon{fill:#552222;}#mermaid-svg-cTqFX1zYNPy0Zsvb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cTqFX1zYNPy0Zsvb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cTqFX1zYNPy0Zsvb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cTqFX1zYNPy0Zsvb .marker.cross{stroke:#333333;}#mermaid-svg-cTqFX1zYNPy0Zsvb svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cTqFX1zYNPy0Zsvb p{margin:0;}#mermaid-svg-cTqFX1zYNPy0Zsvb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cTqFX1zYNPy0Zsvb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cTqFX1zYNPy0Zsvb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-cTqFX1zYNPy0Zsvb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-cTqFX1zYNPy0Zsvb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-cTqFX1zYNPy0Zsvb .sequenceNumber{fill:white;}#mermaid-svg-cTqFX1zYNPy0Zsvb #sequencenumber{fill:#333;}#mermaid-svg-cTqFX1zYNPy0Zsvb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-cTqFX1zYNPy0Zsvb .messageText{fill:#333;stroke:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cTqFX1zYNPy0Zsvb .labelText,#mermaid-svg-cTqFX1zYNPy0Zsvb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .loopText,#mermaid-svg-cTqFX1zYNPy0Zsvb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cTqFX1zYNPy0Zsvb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-cTqFX1zYNPy0Zsvb .noteText,#mermaid-svg-cTqFX1zYNPy0Zsvb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-cTqFX1zYNPy0Zsvb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cTqFX1zYNPy0Zsvb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cTqFX1zYNPy0Zsvb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cTqFX1zYNPy0Zsvb .actorPopupMenu{position:absolute;}#mermaid-svg-cTqFX1zYNPy0Zsvb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-cTqFX1zYNPy0Zsvb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cTqFX1zYNPy0Zsvb .actor-man circle,#mermaid-svg-cTqFX1zYNPy0Zsvb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-cTqFX1zYNPy0Zsvb :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}REQUEST stream=1REQUEST stream=3RESPONSE stream=3(先完成)RESPONSE stream=1(后完成)按 stream_id 归还各自的 call slotPING(连接级,stream=0)PONG(不改变任何调用的状态)
设计要点:
- 长度字段表达的是帧的总长度;解码器必须能返回"半包,需要更多字节"(need_more)并按增量解析推进,而不是要求一次读到整帧。
- 最大帧长必须有限制:否则一个恶意/错乱的 4 字节长度就能让对端预分配 4GB。控制帧(如协商帧)应有独立的、更小的上限,且在分配内存之前检查。
- 类型字段与标志位分离:压缩、可靠投递、有序扩展都放 flags,避免为每种组合定义新类型。
- stream_id 分配:单调递增、跳过 0、预留高位;复用同一连接上的并发调用。
3.2 粘包与半包
TCP 是字节流。一次 read 可能拿到 0.3 个帧,也可能拿到 7 个帧。正确做法是增量解码 + 环形/线性缓冲:
while (true) {
auto need = decoder::peek_header(buffer); // 只读头部,不移动数据
if (!need) return; // 半包:等更多字节
if (need->length > max_frame) { close_with(protocol_error); return; }
if (buffer.size() < need->length) return; // 半包
handler(decoder::decode(buffer, need->length));
buffer.consume(need->length);
}
三个坑:
增量解码的判定顺序(先看头、再判上限、最后才等待整帧):
#mermaid-svg-dD9qqtNZm85J25YZ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dD9qqtNZm85J25YZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dD9qqtNZm85J25YZ .error-icon{fill:#552222;}#mermaid-svg-dD9qqtNZm85J25YZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dD9qqtNZm85J25YZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dD9qqtNZm85J25YZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dD9qqtNZm85J25YZ .marker.cross{stroke:#333333;}#mermaid-svg-dD9qqtNZm85J25YZ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dD9qqtNZm85J25YZ p{margin:0;}#mermaid-svg-dD9qqtNZm85J25YZ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-dD9qqtNZm85J25YZ .cluster-label text{fill:#333;}#mermaid-svg-dD9qqtNZm85J25YZ .cluster-label span{color:#333;}#mermaid-svg-dD9qqtNZm85J25YZ .cluster-label span p{background-color:transparent;}#mermaid-svg-dD9qqtNZm85J25YZ .label text,#mermaid-svg-dD9qqtNZm85J25YZ span{fill:#333;color:#333;}#mermaid-svg-dD9qqtNZm85J25YZ .node rect,#mermaid-svg-dD9qqtNZm85J25YZ .node circle,#mermaid-svg-dD9qqtNZm85J25YZ .node ellipse,#mermaid-svg-dD9qqtNZm85J25YZ .node polygon,#mermaid-svg-dD9qqtNZm85J25YZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dD9qqtNZm85J25YZ .rough-node .label text,#mermaid-svg-dD9qqtNZm85J25YZ .node .label text,#mermaid-svg-dD9qqtNZm85J25YZ .image-shape .label,#mermaid-svg-dD9qqtNZm85J25YZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-dD9qqtNZm85J25YZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dD9qqtNZm85J25YZ .rough-node .label,#mermaid-svg-dD9qqtNZm85J25YZ .node .label,#mermaid-svg-dD9qqtNZm85J25YZ .image-shape .label,#mermaid-svg-dD9qqtNZm85J25YZ .icon-shape .label{text-align:center;}#mermaid-svg-dD9qqtNZm85J25YZ .node.clickable{cursor:pointer;}#mermaid-svg-dD9qqtNZm85J25YZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dD9qqtNZm85J25YZ .arrowheadPath{fill:#333333;}#mermaid-svg-dD9qqtNZm85J25YZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dD9qqtNZm85J25YZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dD9qqtNZm85J25YZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dD9qqtNZm85J25YZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dD9qqtNZm85J25YZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dD9qqtNZm85J25YZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dD9qqtNZm85J25YZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dD9qqtNZm85J25YZ .cluster text{fill:#333;}#mermaid-svg-dD9qqtNZm85J25YZ .cluster span{color:#333;}#mermaid-svg-dD9qqtNZm85J25YZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dD9qqtNZm85J25YZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dD9qqtNZm85J25YZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-dD9qqtNZm85J25YZ .icon-shape,#mermaid-svg-dD9qqtNZm85J25YZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dD9qqtNZm85J25YZ .icon-shape p,#mermaid-svg-dD9qqtNZm85J25YZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dD9qqtNZm85J25YZ .icon-shape .label rect,#mermaid-svg-dD9qqtNZm85J25YZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dD9qqtNZm85J25YZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dD9qqtNZm85J25YZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dD9qqtNZm85J25YZ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
否
是
否
是
收到一块字节
缓冲里够 16B 头吗?
等待更多字节(半包)
长度 <= max_frame?
立即关闭:protocol_error不分配、不复制
缓冲里够整帧吗?
等待更多字节(半包)
解码 + 分发
consume 已消费字节
3.3 版本协商
"同源同版本互通"是玩具框架的标志。真实部署里,滚动升级意味着新旧版本必须能共存。协商设计有三条硬约束:
- 载体必须是旧版本会静默忽略的东西。如果旧版本对未知帧类型直接断连、或对非零 reserved 字段直接报错,那么任何"新帧/新字段"方案都无法后向兼容。可用的载体通常只有:既有但未使用的帧类型、既有帧的"已忽略"字段。
- 必须 fail-closed:未知版本、未知能力位、未知 TLV 一律显式拒绝(protocol_error),不许"不认识就跳过"。
- 协商失败必须可诊断:不能把版本拒绝映射成"连接被关闭"这种无信息错误;要有明确的错误码与 GOAWAY code。
一个可用的最小协商帧(版本 2,24 字节头 + TLV):
offset 0 u16 version(2)
offset 2 u16 flags
offset 4 u16 min_version
offset 6 u16 max_version
offset 8 u8 status // 0 ok / 1 version mismatch / 2 bad settings / 3 unauthorized
offset 9 u8 reserved
offset 10 u16 bitmap // 能力位
offset 12 u32 max_decompressed // 解压上限(0=默认)
offset 16 u32 max_compression_ratio
offset 20 u16 tlv_len
offset 22 u16 reserved2
[TLV: u16 type, u16 len, value …]
TLV 白名单之外的任何 type 都必须报 protocol_error——否则就是"半启用":对端以为协商成功,本端其实没实现。
3.4 状态机
协商把连接分成几个状态:legacy(对端可能是旧版本)、negotiating、negotiated、fatal。允许的状态转移必须枚举完整,包括控制帧:
- PING/PONG/SETTINGS_ACK/RST 不改变协商状态;
- 旧版本对端收到 REQUEST/ONE_WAY 才判定为 legacy;收到未知 DATA 必须 fatal;
- strict 模式下,协商完成前不允许发送任何应用帧;ONE_WAY 调用在协商完成前应直接失败(failed_precondition),而不是"先返回成功再丢包"。
#mermaid-svg-kG0FwFPpaVGFb7ex{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kG0FwFPpaVGFb7ex .error-icon{fill:#552222;}#mermaid-svg-kG0FwFPpaVGFb7ex .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kG0FwFPpaVGFb7ex .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kG0FwFPpaVGFb7ex .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kG0FwFPpaVGFb7ex .marker.cross{stroke:#333333;}#mermaid-svg-kG0FwFPpaVGFb7ex svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kG0FwFPpaVGFb7ex p{margin:0;}#mermaid-svg-kG0FwFPpaVGFb7ex defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-kG0FwFPpaVGFb7ex g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-kG0FwFPpaVGFb7ex g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-kG0FwFPpaVGFb7ex g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-kG0FwFPpaVGFb7ex g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-kG0FwFPpaVGFb7ex g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-kG0FwFPpaVGFb7ex .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-kG0FwFPpaVGFb7ex .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-kG0FwFPpaVGFb7ex .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-kG0FwFPpaVGFb7ex .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kG0FwFPpaVGFb7ex .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-kG0FwFPpaVGFb7ex .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-kG0FwFPpaVGFb7ex .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-kG0FwFPpaVGFb7ex .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kG0FwFPpaVGFb7ex .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kG0FwFPpaVGFb7ex .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kG0FwFPpaVGFb7ex .edgeLabel .label text{fill:#333;}#mermaid-svg-kG0FwFPpaVGFb7ex .label div .edgeLabel{color:#333;}#mermaid-svg-kG0FwFPpaVGFb7ex .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-kG0FwFPpaVGFb7ex .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-kG0FwFPpaVGFb7ex .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-kG0FwFPpaVGFb7ex .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-kG0FwFPpaVGFb7ex .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-kG0FwFPpaVGFb7ex .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kG0FwFPpaVGFb7ex .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kG0FwFPpaVGFb7ex #statediagram-barbEnd{fill:#333333;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kG0FwFPpaVGFb7ex .cluster-label,#mermaid-svg-kG0FwFPpaVGFb7ex .nodeLabel{color:#131300;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-kG0FwFPpaVGFb7ex .note-edge{stroke-dasharray:5;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-note text{fill:black;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram-note .nodeLabel{color:black;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagram .edgeLabel{color:red;}#mermaid-svg-kG0FwFPpaVGFb7ex #dependencyStart,#mermaid-svg-kG0FwFPpaVGFb7ex #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-kG0FwFPpaVGFb7ex .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kG0FwFPpaVGFb7ex :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
连接建立
收到/发出 SETTINGS(v2)
双方 v2 且版本区间含 2
版本不匹配 / TLV 非法 / 超时
收到 DATA / 畸形帧
协议错误 / GOAWAY
关闭连接
legacy
negotiating
negotiated
fatal
compat 模式:对端可能是 2.0.0只按精确帧类型表判定
strict 模式:应用帧必须等协商完成ONE_WAY 直接 failed_precondition
四、传输层:事件驱动与背压
4.1 两种 I/O 模型
- epoll(Linux,ET 边缘触发):只注册"可读/可写"事件,必须一次读到 EAGAIN,否则事件不再触发;写侧需要自己的发送队列。
- IOCP(Windows):完成端口,提交 overlapped 操作,完成时回调。天然支持"提交-完成"配对,但要注意每个 socket 上的并发发送数、以及取消/关闭时的完成顺序。
抽象出的传输契约应该只有两个方向:
struct transport {
virtual void on_data(span<const std::byte>) = 0; // 收到数据
virtual void send(span<const std::byte>) = 0; // 要发数据(框架已组好帧)
};
所有粘包、复用、加密、压缩都在这个契约之上,而不是散落在各个 backend 里。
4.2 背压
发送队列必须有界。无界队列在网络卡顿时会吃光内存;有界队列则必须决定"满了怎么办":
- 阻塞调用方(可能死锁,尤其在事件循环线程里);
- 返回错误(resource_exhausted,快速失败);
- 丢弃(仅限不可靠消息,且必须计数)。
正确做法通常是:可靠消息返回错误或排队到上限,不可靠消息按策略丢弃并计数;同时用应用层流控(窗口)在更早的时间点通知对端减速。
4.3 连接生命周期
- keepalive / ping:必须有,且要能区分"对端忙"与"对端死";
- 半开检测:TCP 半开连接不会自动报错,只能靠心跳超时或写失败;
- 重连:指数退避 + 抖动;重连后必须重置连接级状态(流 ID 分配、窗口、协商结果、在途 call slot 与 keepalive 定时器)。注意:服务端幂等去重表不是连接级状态,它必须跨重连存活(按 TTL/容量有界),否则重连后的重试会重复执行。
- 关闭:显式控制帧(GOAWAY/close),不能只依赖 FIN。
五、调用模型:同步、异步、协程共享一个状态机
5.1 三种写法,一套内核
// 同步(协程挂起实现,看起来像阻塞)
auto r = client->call<resp>(req, std::chrono::milliseconds(500));
// 异步(future/handle)
auto fut = client->call_async<resp>(req, 500ms);
auto r2 = fut.get();
// 协程
auto r3 = co_await client->call_co<resp>(req, 500ms);
关键不是提供三种 API,而是三者共享同一套 call slot 状态机:idle → pending → completed/cancelled/timeout。否则就会出现"同步路径处理了超时、异步路径忘了"这类分叉缺陷。
5.2 deadline 与取消
- deadline 必须端到端传播:调用方设置 → 线协议携带剩余时间(绝对值会在时钟漂移下出错,通常传"剩余毫秒")→ 服务端据此决定是否还值得执行。
- 取消必须是显式消息(RST/ATTENTION),并且服务端要能中断正在执行的调用(例如 SQL 的 attention、长计算的协作取消点)。
- 超时必须由驱动者保证:裸客户端如果 get() 阻塞等待条件变量,而没有任何线程在超时后唤醒它,就会永久挂起。超时检查必须并入 get/poll 的谓词,或有独立的定时器轮。
5.3 一次调用的完整状态
alloc slot → 编码 → 入发送队列 → 写 socket → 等待(带 deadline)
↓ 超时/取消 ↓ 响应到达
标记 cancelled + 发 RST 解码 → 匹配 stream_id → 唤醒等待者
↓ ↓
回收 slot(幂等,恰好一次) 回收 slot(幂等,恰好一次)
"回收恰好一次"是硬要求:超时与响应可能同时到达,必须用原子状态迁移(CAS idle→completed / idle→cancelled)决定谁负责回收。
#mermaid-svg-pdT2xGZRf3z7kmEB{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pdT2xGZRf3z7kmEB .error-icon{fill:#552222;}#mermaid-svg-pdT2xGZRf3z7kmEB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pdT2xGZRf3z7kmEB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pdT2xGZRf3z7kmEB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pdT2xGZRf3z7kmEB .marker.cross{stroke:#333333;}#mermaid-svg-pdT2xGZRf3z7kmEB svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pdT2xGZRf3z7kmEB p{margin:0;}#mermaid-svg-pdT2xGZRf3z7kmEB defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-pdT2xGZRf3z7kmEB g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-pdT2xGZRf3z7kmEB g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-pdT2xGZRf3z7kmEB g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-pdT2xGZRf3z7kmEB g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-pdT2xGZRf3z7kmEB g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-pdT2xGZRf3z7kmEB .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-pdT2xGZRf3z7kmEB .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-pdT2xGZRf3z7kmEB .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-pdT2xGZRf3z7kmEB .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pdT2xGZRf3z7kmEB .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-pdT2xGZRf3z7kmEB .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-pdT2xGZRf3z7kmEB .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-pdT2xGZRf3z7kmEB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pdT2xGZRf3z7kmEB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pdT2xGZRf3z7kmEB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pdT2xGZRf3z7kmEB .edgeLabel .label text{fill:#333;}#mermaid-svg-pdT2xGZRf3z7kmEB .label div .edgeLabel{color:#333;}#mermaid-svg-pdT2xGZRf3z7kmEB .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-pdT2xGZRf3z7kmEB .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-pdT2xGZRf3z7kmEB .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-pdT2xGZRf3z7kmEB .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-pdT2xGZRf3z7kmEB .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-pdT2xGZRf3z7kmEB .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pdT2xGZRf3z7kmEB .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pdT2xGZRf3z7kmEB #statediagram-barbEnd{fill:#333333;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pdT2xGZRf3z7kmEB .cluster-label,#mermaid-svg-pdT2xGZRf3z7kmEB .nodeLabel{color:#131300;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-pdT2xGZRf3z7kmEB .note-edge{stroke-dasharray:5;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-note text{fill:black;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram-note .nodeLabel{color:black;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagram .edgeLabel{color:red;}#mermaid-svg-pdT2xGZRf3z7kmEB #dependencyStart,#mermaid-svg-pdT2xGZRf3z7kmEB #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-pdT2xGZRf3z7kmEB .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pdT2xGZRf3z7kmEB :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
slot 池取出
编码并写入发送队列
响应到达(匹配 stream_id)
调用方取消
deadline 到期
回收 slot
发 RST + 回收
发 RST + 回收
idle
pending
completed
cancelled
timeout
超时与响应可能同时到达CAS 决定唯一的回收者
六、可靠性:重试、幂等、去重、有序
6.1 重试的前提是幂等
网络错误分三类:确定没到(连接建立失败)、不确定(写成功但读超时)、确定到了(收到了应用层错误)。只有前两类可以重试,第二类必须靠幂等键去重。
struct call_options {
std::string idempotency_key; // 由调用方生成,重试时复用
int max_attempts = 3;
std::chrono::milliseconds backoff_base{10};
};
服务端维护去重表:key → (状态, 结果),重放时直接返回已提交结果。去重表必须有界(LRU + TTL),并且拒绝旧消息(单调序号/版本),否则节点重启后重放会复活已删除的状态。
6.2 有序通道
"同一个目标上的调用按提交顺序执行"需要 lane(通道)概念:每个 lane 一条串行队列,跨 lane 并行。实现要点:
- lane 内序号单调,接收端按序号交付,缺号等待;
- 去重与缺号必须共用同一个水位(watermark),不能两套计数;
- lane 关闭时必须 fail_all,且已入队的调用要么执行要么明确失败,不能静默丢弃。
6.3 可靠投递 ≠ 恰好一次
行业里没有"网络层恰好一次"。能给的只有:至少一次投递 + 单调去重锚 = 应用层恰好一次效果。锚可以是自增 ID、版本号或幂等键;关键是它必须单调且持久。
#mermaid-svg-EwjaBtL42fgMLKbx{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EwjaBtL42fgMLKbx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EwjaBtL42fgMLKbx .error-icon{fill:#552222;}#mermaid-svg-EwjaBtL42fgMLKbx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EwjaBtL42fgMLKbx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EwjaBtL42fgMLKbx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EwjaBtL42fgMLKbx .marker.cross{stroke:#333333;}#mermaid-svg-EwjaBtL42fgMLKbx svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EwjaBtL42fgMLKbx p{margin:0;}#mermaid-svg-EwjaBtL42fgMLKbx .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-EwjaBtL42fgMLKbx .cluster-label text{fill:#333;}#mermaid-svg-EwjaBtL42fgMLKbx .cluster-label span{color:#333;}#mermaid-svg-EwjaBtL42fgMLKbx .cluster-label span p{background-color:transparent;}#mermaid-svg-EwjaBtL42fgMLKbx .label text,#mermaid-svg-EwjaBtL42fgMLKbx span{fill:#333;color:#333;}#mermaid-svg-EwjaBtL42fgMLKbx .node rect,#mermaid-svg-EwjaBtL42fgMLKbx .node circle,#mermaid-svg-EwjaBtL42fgMLKbx .node ellipse,#mermaid-svg-EwjaBtL42fgMLKbx .node polygon,#mermaid-svg-EwjaBtL42fgMLKbx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EwjaBtL42fgMLKbx .rough-node .label text,#mermaid-svg-EwjaBtL42fgMLKbx .node .label text,#mermaid-svg-EwjaBtL42fgMLKbx .image-shape .label,#mermaid-svg-EwjaBtL42fgMLKbx .icon-shape .label{text-anchor:middle;}#mermaid-svg-EwjaBtL42fgMLKbx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EwjaBtL42fgMLKbx .rough-node .label,#mermaid-svg-EwjaBtL42fgMLKbx .node .label,#mermaid-svg-EwjaBtL42fgMLKbx .image-shape .label,#mermaid-svg-EwjaBtL42fgMLKbx .icon-shape .label{text-align:center;}#mermaid-svg-EwjaBtL42fgMLKbx .node.clickable{cursor:pointer;}#mermaid-svg-EwjaBtL42fgMLKbx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EwjaBtL42fgMLKbx .arrowheadPath{fill:#333333;}#mermaid-svg-EwjaBtL42fgMLKbx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EwjaBtL42fgMLKbx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EwjaBtL42fgMLKbx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EwjaBtL42fgMLKbx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EwjaBtL42fgMLKbx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EwjaBtL42fgMLKbx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EwjaBtL42fgMLKbx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EwjaBtL42fgMLKbx .cluster text{fill:#333;}#mermaid-svg-EwjaBtL42fgMLKbx .cluster span{color:#333;}#mermaid-svg-EwjaBtL42fgMLKbx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EwjaBtL42fgMLKbx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EwjaBtL42fgMLKbx rect.text{fill:none;stroke-width:0;}#mermaid-svg-EwjaBtL42fgMLKbx .icon-shape,#mermaid-svg-EwjaBtL42fgMLKbx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EwjaBtL42fgMLKbx .icon-shape p,#mermaid-svg-EwjaBtL42fgMLKbx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EwjaBtL42fgMLKbx .icon-shape .label rect,#mermaid-svg-EwjaBtL42fgMLKbx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EwjaBtL42fgMLKbx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EwjaBtL42fgMLKbx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EwjaBtL42fgMLKbx :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
应用层错误
确定未到达
不确定(超时)
是
否
发起调用(带 idempotency_key)
发送请求
结果?
不重试:错误可诊断
退避后重试
带同一 key 重试
服务端去重表命中?
返回已提交结果(幂等)
执行并记录 key → 结果
返回结果
去重表有界:LRU + TTL,拒绝旧序号
七、流控与拥塞控制
TCP 自己的窗口只能保护内核缓冲,保护不了应用层队列。应用层流控通常包含三件套:
窗口更新必须是幂等且单调的:如果采用增量式更新(例如 HTTP/2 的 WINDOW_UPDATE 携带的是增量),重复投递不得被重复累加,且窗口只能通过增量增大;要缩小窗口必须通过重新协商或新版本,而不是发一个"更小的窗口值"。零窗口时必须发送探测(zero-window probe),否则双方会永久僵死。
接收方发送方接收方发送方#mermaid-svg-s9r1enjHlmWZaXog{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s9r1enjHlmWZaXog .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s9r1enjHlmWZaXog .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s9r1enjHlmWZaXog .error-icon{fill:#552222;}#mermaid-svg-s9r1enjHlmWZaXog .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s9r1enjHlmWZaXog .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s9r1enjHlmWZaXog .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s9r1enjHlmWZaXog .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s9r1enjHlmWZaXog .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s9r1enjHlmWZaXog .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s9r1enjHlmWZaXog .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s9r1enjHlmWZaXog .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s9r1enjHlmWZaXog .marker.cross{stroke:#333333;}#mermaid-svg-s9r1enjHlmWZaXog svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s9r1enjHlmWZaXog p{margin:0;}#mermaid-svg-s9r1enjHlmWZaXog .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s9r1enjHlmWZaXog text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-s9r1enjHlmWZaXog .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-s9r1enjHlmWZaXog .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-s9r1enjHlmWZaXog .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-s9r1enjHlmWZaXog .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-s9r1enjHlmWZaXog #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-s9r1enjHlmWZaXog .sequenceNumber{fill:white;}#mermaid-svg-s9r1enjHlmWZaXog #sequencenumber{fill:#333;}#mermaid-svg-s9r1enjHlmWZaXog #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-s9r1enjHlmWZaXog .messageText{fill:#333;stroke:none;}#mermaid-svg-s9r1enjHlmWZaXog .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s9r1enjHlmWZaXog .labelText,#mermaid-svg-s9r1enjHlmWZaXog .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-s9r1enjHlmWZaXog .loopText,#mermaid-svg-s9r1enjHlmWZaXog .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-s9r1enjHlmWZaXog .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-s9r1enjHlmWZaXog .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-s9r1enjHlmWZaXog .noteText,#mermaid-svg-s9r1enjHlmWZaXog .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-s9r1enjHlmWZaXog .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s9r1enjHlmWZaXog .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s9r1enjHlmWZaXog .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s9r1enjHlmWZaXog .actorPopupMenu{position:absolute;}#mermaid-svg-s9r1enjHlmWZaXog .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-s9r1enjHlmWZaXog .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s9r1enjHlmWZaXog .actor-man circle,#mermaid-svg-s9r1enjHlmWZaXog line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-s9r1enjHlmWZaXog :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}loop[持续发送]窗口 = 0 时发送方停止 DATA并启动 zero-window probe 定时器初始窗口 W(协商/默认)DATA(占用窗口)消费数据,腾出空间WINDOW_UPDATE(+N)窗口 += N(幂等去重,不重复累加)WINDOW_UPDATE(+N)(恢复)
八、服务治理
8.1 注册与发现
- 注册:节点上报地址、元数据、健康状态;必须带 TTL/lease,避免死节点永久留在目录里;
- 发现:客户端订阅目录变更(watch),或用长轮询拉取;
- 健康检查:主动探测(TCP/HTTP/ICMP)+ 被动熔断反馈;探测结果必须有单一写者更新状态,并做去抖(连续 N 次失败才判死,连续 M 次成功才恢复)。
8.2 负载均衡
常见策略:轮询、加权、最少连接、一致性哈希、P2C(随机两个取较优)。工程上比策略更重要的是:
- 排除已熔断/不健康的节点;
- 保留重试预算(重试不能放大成雪崩);
- 连接池与节点选择的亲和性(避免每次调用换连接导致 TLS 重握手)。
8.3 熔断、限流、隔离、降级
- 熔断:closed → open(失败率/连续失败超阈值)→ half-open(放少量探测);
- 限流:令牌桶/漏桶;分布式场景需要中心或配额分片;
- 隔离(bulkhead):按依赖分配并发额度,避免一个慢依赖拖垮全部线程;
- 降级:返回缓存/默认值,但必须显式标记,禁止"静默成功"。
#mermaid-svg-sGZYJo5CNL5dF0LW{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sGZYJo5CNL5dF0LW .error-icon{fill:#552222;}#mermaid-svg-sGZYJo5CNL5dF0LW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sGZYJo5CNL5dF0LW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sGZYJo5CNL5dF0LW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sGZYJo5CNL5dF0LW .marker.cross{stroke:#333333;}#mermaid-svg-sGZYJo5CNL5dF0LW svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sGZYJo5CNL5dF0LW p{margin:0;}#mermaid-svg-sGZYJo5CNL5dF0LW .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW .cluster-label text{fill:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW .cluster-label span{color:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW .cluster-label span p{background-color:transparent;}#mermaid-svg-sGZYJo5CNL5dF0LW .label text,#mermaid-svg-sGZYJo5CNL5dF0LW span{fill:#333;color:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW .node rect,#mermaid-svg-sGZYJo5CNL5dF0LW .node circle,#mermaid-svg-sGZYJo5CNL5dF0LW .node ellipse,#mermaid-svg-sGZYJo5CNL5dF0LW .node polygon,#mermaid-svg-sGZYJo5CNL5dF0LW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sGZYJo5CNL5dF0LW .rough-node .label text,#mermaid-svg-sGZYJo5CNL5dF0LW .node .label text,#mermaid-svg-sGZYJo5CNL5dF0LW .image-shape .label,#mermaid-svg-sGZYJo5CNL5dF0LW .icon-shape .label{text-anchor:middle;}#mermaid-svg-sGZYJo5CNL5dF0LW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sGZYJo5CNL5dF0LW .rough-node .label,#mermaid-svg-sGZYJo5CNL5dF0LW .node .label,#mermaid-svg-sGZYJo5CNL5dF0LW .image-shape .label,#mermaid-svg-sGZYJo5CNL5dF0LW .icon-shape .label{text-align:center;}#mermaid-svg-sGZYJo5CNL5dF0LW .node.clickable{cursor:pointer;}#mermaid-svg-sGZYJo5CNL5dF0LW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sGZYJo5CNL5dF0LW .arrowheadPath{fill:#333333;}#mermaid-svg-sGZYJo5CNL5dF0LW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sGZYJo5CNL5dF0LW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sGZYJo5CNL5dF0LW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sGZYJo5CNL5dF0LW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sGZYJo5CNL5dF0LW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sGZYJo5CNL5dF0LW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sGZYJo5CNL5dF0LW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sGZYJo5CNL5dF0LW .cluster text{fill:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW .cluster span{color:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sGZYJo5CNL5dF0LW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sGZYJo5CNL5dF0LW rect.text{fill:none;stroke-width:0;}#mermaid-svg-sGZYJo5CNL5dF0LW .icon-shape,#mermaid-svg-sGZYJo5CNL5dF0LW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sGZYJo5CNL5dF0LW .icon-shape p,#mermaid-svg-sGZYJo5CNL5dF0LW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sGZYJo5CNL5dF0LW .icon-shape .label rect,#mermaid-svg-sGZYJo5CNL5dF0LW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sGZYJo5CNL5dF0LW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sGZYJo5CNL5dF0LW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sGZYJo5CNL5dF0LW :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
closed
open
half-open
是
否
调用方
节点选择
注册中心目录(watch/长轮询)
负载均衡(轮询/P2C/一致性哈希)
熔断器状态?
发起调用
直接失败或降级
放少量探测请求
成功?
返回结果 + 关闭熔断计数
失败计数 + 退避重试(预算内)
限流/舱壁:额度内才执行
九、可观测性
9.1 分布式追踪
W3C traceparent 格式(Trace Context, W3C Recommendation 23 Nov 2021):
<version:2 hex>-<trace-id:32 hex>-<parent-id:16 hex>-<trace-flags:2 hex>
例: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
要点:
- 字段官方名是 parent-id(追踪系统里常称 span-id);version ff 非法,当前规范版本为 00;trace-id 与 parent-id 全零非法;
- trace-flags 当前只定义了最低位 sampled(0x01),其余位必须置 0;
- trace/span 的父子关系只能由消息收发对建立,不能靠时间戳;
- 采样决策要随上下文传播(否则下游不知道是否采样);修改 sampled 时必须同时更新 parent-id;
- 跨进程传递要在协议里留字段,不能在应用 payload 里"约定"。
下游服务中间服务客户端进程下游服务中间服务客户端进程#mermaid-svg-LMAHF2cTRpxZ1qrd{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LMAHF2cTRpxZ1qrd .error-icon{fill:#552222;}#mermaid-svg-LMAHF2cTRpxZ1qrd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LMAHF2cTRpxZ1qrd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LMAHF2cTRpxZ1qrd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LMAHF2cTRpxZ1qrd .marker.cross{stroke:#333333;}#mermaid-svg-LMAHF2cTRpxZ1qrd svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LMAHF2cTRpxZ1qrd p{margin:0;}#mermaid-svg-LMAHF2cTRpxZ1qrd .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LMAHF2cTRpxZ1qrd text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LMAHF2cTRpxZ1qrd .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LMAHF2cTRpxZ1qrd .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LMAHF2cTRpxZ1qrd #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LMAHF2cTRpxZ1qrd .sequenceNumber{fill:white;}#mermaid-svg-LMAHF2cTRpxZ1qrd #sequencenumber{fill:#333;}#mermaid-svg-LMAHF2cTRpxZ1qrd #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LMAHF2cTRpxZ1qrd .messageText{fill:#333;stroke:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LMAHF2cTRpxZ1qrd .labelText,#mermaid-svg-LMAHF2cTRpxZ1qrd .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .loopText,#mermaid-svg-LMAHF2cTRpxZ1qrd .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LMAHF2cTRpxZ1qrd .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LMAHF2cTRpxZ1qrd .noteText,#mermaid-svg-LMAHF2cTRpxZ1qrd .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LMAHF2cTRpxZ1qrd .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LMAHF2cTRpxZ1qrd .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LMAHF2cTRpxZ1qrd .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LMAHF2cTRpxZ1qrd .actorPopupMenu{position:absolute;}#mermaid-svg-LMAHF2cTRpxZ1qrd .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LMAHF2cTRpxZ1qrd .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LMAHF2cTRpxZ1qrd .actor-man circle,#mermaid-svg-LMAHF2cTRpxZ1qrd line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LMAHF2cTRpxZ1qrd :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}父子关系只来自收发对,不靠时间戳对齐生成 trace-id / parent-id(span A)1traceparent: 00-<trace>-<A>-012以 <A> 为父创建 span B3traceparent: 00-<trace>-<B>-014以 <B> 为父创建 span C5响应(携带同一 trace 上下文)6响应7
9.2 Metrics 与日志
- 指标:调用数、错误数、在途数、队列深度、延迟直方图;未配置时必须零成本(一次原子读后直接返回)。
- 日志:异步、无锁环形队列 + 后台批量落盘;结构化字段;热路径禁止格式化。
9.3 AOP 拦截
客户端调用、服务端分发、序列化、传输、重试、熔断都可以开放拦截点。关键是未注册拦截器时零成本:空链判断必须是一次指针比较,不能每次调用构造 vector。
十、安全
- 传输:TLS(ALPN 区分协议),证书校验不可关闭;
- 认证:mTLS 或 token;token 必须能映射到能力域(读/写/前缀),而不是"一个万能 admin token";
- 授权:默认拒绝(fail-closed),未知能力位拒绝;
- 审计:认证/授权失败必须可观测,且 token 不得进日志。
十一、性能工程的真相
十二、常见坑清单
- 帧长度不设上限 → 内存炸弹;
- 半包路径先复制再检查 → 同上;
- 用"输出为空"判断编码失败 → 合法空消息被误判;
- 超时只设置不驱动 → 永久挂起;
- 重试不幂等 → 重复扣款;
- 去重表无界 → 内存泄漏;
- 窗口更新非单调 → 窗口抖动/死锁;
- 协商帧被当作普通数据帧走可丢队列 → 协商状态丢失;
- 关闭只靠 FIN → 半开连接残留;
- 影子状态(缓存/注册表/健康状态)多写者 → 状态撕裂;
- 用 sizeof(struct) 当线格式(对齐填充会变)→ 跨编译器/平台错位;
- 重连时把幂等去重表当连接级状态清空 → 重试重复执行。
十三、结语
RPC 框架的难度从来不在"把结构体变成字节",而在于:让每一个失败路径都有确定的结果,让每一个状态只有一个写者,让每一次等待都有对应的解除等待。
把这三点做到,同步/异步/协程只是同一状态机的三种外观;做不到,再多的 API 也只是把不确定性包装得更漂亮。

