选 protobuf 为序列化协议最稳妥,因其跨语言支持好、idl 易维护、c++ 零拷贝友好;需开启 optional/oneof 保证兼容性,禁用裸指针与 stl 容器,改用 repeated 字段。

怎么选序列化协议才不卡在跨语言和性能上
自定义 RPC 框架里,序列化不是“能跑就行”,而是决定你后续能不能加 Go/Python 服务、压测时 latency 突然翻倍的根源。别一上来就手写二进制格式——protobuf 是当前最稳妥的起点,它生成的 C++ 代码零拷贝友好、ParseFromString 和 SerializeToString 接口稳定、IDL 易维护;flatbuffers 虽快但 C++ 运行时依赖 schema,调试时看不到原始字段名,上线后改个字段容易 silently 失败。
常见错误现象:Deserialize failed: invalid wire type,往往是 client 用新版 proto 发请求,server 还在用旧版头文件编译,没做 forward/backward 兼容校验。
- 必须开启
proto2的optional或proto3的oneof来控制字段可选性,避免默认值污染语义 - 禁止在 message 里嵌套裸指针或 STL 容器(如
std::vector<int></int>),一律用 repeated 字段,否则序列化结果不可控 - 如果真要极致性能且只跑 C++,可以后期用
capnproto替换,但它不支持浮点数 NaN 校验,线上遇到NaN会直接 abort
如何让网络层不成为吞吐瓶颈
很多人用 boost::asio 写完 accept + async_read 就以为搞定了,结果 QPS 上不去还查不出原因。根本问题是没把连接生命周期和业务线程解耦:每个 connection 对应一个 io_context 实例?错,应该共享一个或少数几个 io_context,再配固定数量的 thread_pool 去 dispatch 请求。
典型坑:async_read 回调里直接调用耗时函数(比如数据库查询),导致整个 io_context 被 block,后续所有连接卡住。
立即学习“C++免费学习笔记(深入)”;
- 收包后立即用
post把解析+处理逻辑扔进业务线程池,io_context只管 I/O - 包头必须带长度字段,用
async_read先读 4 字节再读 body,别用 delimiter(比如 \n)——HTTP/2 都不用换行分隔了 - 禁用 Nagle 算法:
socket.set_option(tcp::no_delay(true)),否则小包延迟飙升
怎样设计请求 ID 和超时机制才不会丢响应
RPC 最怕“发出去没回音”,表面是网络问题,实际常因 request_id 重复或 timeout 清理策略不对。C++ 里不能靠全局递增 int——多线程下 ++g_req_id 不安全,也不能用 std::chrono::steady_clock::now().time_since_epoch().count() 当 ID,纳秒级时间戳在高并发下极易碰撞。
正确做法是组合:线程局部 ID + 时间戳低 32 位 + 随机 salt。例如 uint64_t req_id = (tid 。
- 每个
rpc_client实例维护自己的std::unordered_map<uint64_t std::promise>></uint64_t>,key 就是这个 req_id - 发送前插入 map,同时启动
std::thread或 timer(推荐asio::steady_timer)等待 timeout,超时则promise.set_exception - 收到响应后先查 map,存在就
set_value并 erase;不存在说明已超时,直接丢弃——别试图重试,重试逻辑应在上层业务控制
为什么服务发现和负载均衡不能硬编码在 client 里
本地测试时写死 "127.0.0.1:8080" 很方便,但上线后节点扩缩、故障转移、灰度发布全崩。C++ 没有像 Java 那样成熟的注册中心 SDK,得自己对接 etcd 或 consul 的 HTTP API,但千万别每发一次请求都去查一次服务列表。
真实场景下,client 启动时拉取一次全量节点,存进 std::vector<endpoint></endpoint>,再用后台线程定期 GET /v1/health 做健康检查,剔除失联节点。负载策略选 round_robin 就够用,别一上来搞一致性哈希——C++ 里 std::hash<:string></:string> 在不同编译器版本可能不一致,导致同一 key 路由到不同 server。
- etcd watch 接口要用 long polling,别轮询,否则集群压力大
- 节点 IP+端口字符串作为 key 存进 map,不要用
struct sockaddr_in直接 hash,字节序和 padding 会导致跨平台不一致 - 首次连接失败时,别立刻 panic,应 fallback 到本地缓存的节点列表(哪怕过期 30 秒),保证降级可用
真正难的不是写通一个 call,而是当 client 同时发起 10k 并发、server 有 50 个实例、其中 3 个正在滚动更新、etcd 网络抖动 200ms 时,你的 timeout 设置、重试次数、连接复用策略是否还能让 P99 延迟稳定在 50ms 内——这些细节,往往在压测最后一天才暴露。










