XRPC:一个能写进简历的 C++ 高性能分布式 RPC 框架,QPS 13万+
先说一个让很多人沉默的问题
你在简历上写过"熟悉 RPC 框架"吗?
如果面试官接着问:"RPC 调用的完整链路是什么?从客户端发起请求,到服务端返回响应,中间每一步发生了什么?"
能流畅回答的人,大概不到 10%。
能进一步回答"连接池为什么能把 P99 延迟从 10ms 压到 0.2ms"的人,更是凤毛麟角。
这不是他们不努力,而是他们用的学习路径从一开始就走偏了——调过 gRPC,改过 GitHub 上别人的 RPC demo,但从来没有亲手造过一个。
今天这篇文章,就是为想造一个的人写的。
为什么不直接学 gRPC / brpc / srpc?
这个问题我被问过很多次,答案很直接。
gRPC 的核心 C++ 实现超过 50 万行。brpc 也在 10 万行 以上。你打开任何一个文件,迎接你的是层层宏定义、平台兼容代码、协议扩展点……光理清模块依赖关系就要花一周。
更关键的是:这些项目展示的是一个经历了多年打磨的成品。
你看不到 RpcChannel 的异步调用链是怎么一步步设计出来的,看不到 protobuf 反射 dispatch 是在哪个阶段接进去的,看不到连接池的 acquire/release 接口为什么要这样设计,更看不到 msgid 透传是怎么从协议层一路贯穿到链路追踪的。
这些"从0到1"的过程,是任何成品代码都给不了你的东西。
XRPC:4700 行,25 天,从空文件夹到完整 RPC 框架
这个项目叫 XRPC,基于我之前实现的 ReactorX 事件驱动引擎和 NetCore 网络库,向上构建完整的 RPC 框架层。
核心代码 4700+ 行,含测试与压测代码总计约 8600 行,25 天完整教学,每天增量实现,每天结束都能编译运行。
先看架构全貌:

先看压测数据
同一台机器,连接池开启,1000 次顺序 RPC 调用:
P50 延迟: 0.061 ms
P95 延迟: 0.163 ms
P99 延迟: 0.226 ms ← 不到 0.23ms
最大延迟: 0.357 ms
顺序 QPS: 12,821 次/秒
延迟分布:
<0.1ms ████████████████████████████████████ 821次 (82.1%)
<0.2ms ███████ 162次 (16.2%)
<0.3ms 13次 (1.3%)
扩展性曲线,连接池开启 vs 关闭,并发 32 workers:
并发数 │ 无连接池 QPS P99 │ 有连接池 QPS P99 加速比
────────┼────────────────────────┼───────────────────────────────
1 │ 2,564 1.02ms │ 12,500 0.20ms 4.9x
4 │ 7,692 8.62ms │ 50,000 0.20ms 6.5x
16 │ 12,698 10.18ms │ 123,077 0.21ms 9.7x
32 │ 17,778 7.28ms │ 133,333 0.46ms 7.5x
连接池开启,32 并发时 QPS 达到 13.3 万,P99 仍在 0.5ms 以内。
没有连接池的版本,P99 高达 7~10ms,这就是每次 RPC 新建 TCP 连接的代价——每次都要经历三次握手,在高并发下彻底成为瓶颈。这个对比,是 Day 12 压测专题的核心内容,数据是真实跑出来的,不是估算。
三行启动服务端,五行发起调用
接口设计参考 gRPC 风格,学完之后切换任何主流 RPC 框架几乎无缝衔接:
// ══ 第一步:实现业务方法 ══════════════════════════════
class OrderServiceImpl : public order::Order {
public:
void makeOrder(google::protobuf::RpcController*,
const order::MakeOrderRequest* req,
order::MakeOrderResponse* rsp,
google::protobuf::Closure* done) override {
rsp->set_order_id("ORD-" + std::to_string(++seq_));
rsp->set_total_amount(req->quantity() * 99.0);
rsp->set_ret_code(0);
done->Run(); // 框架在此编码响应并回包
}
private:
std::atomic<int> seq_{1000};
};
// ══ 第二步:3 行启动 RpcServer ═══════════════════════
EventLoop loop;
RpcServer server(&loop, InetAddress(9090), /*io_threads=*/4);
server.registerService(std::make_shared<OrderServiceImpl>());
server.start();
loop.loop();
// ══ 第三步:客户端异步调用 ══════════════════════════
auto ctrl = std::make_shared<RpcController>();
ctrl->SetTimeout(3000); // per-call 超时 3s
auto closure = std::make_shared<RpcClosure>([&]() {
printf("订单号: %s 金额: %.2f 元\n",
rsp->order_id().c_str(), rsp->total_amount());
});
auto channel = std::make_shared<RpcChannel>(
&client_loop, InetAddress("127.0.0.1", 9090));
channel->Init(ctrl, req, rsp, closure);
order::Order_Stub stub(channel.get());
stub.makeOrder(ctrl.get(), req.get(), rsp.get(), closure.get());
// ══ 进阶:连接池(零额外代码切换)══════════════════
ConnectionPool pool(InetAddress("127.0.0.1", 9090), /*pool_size=*/8);
pool.start();
auto channel = std::make_shared<RpcChannel>(&pool); // 只改这一行
// 其余调用代码完全相同,框架自动取用空闲连接并归还
从无池切换到有池,只需改一行——RpcChannel 的构造参数从 EventLoop+地址 换成 ConnectionPool,其余调用代码一字不改。
25 天,你会亲手构建什么
整个教程分三个递进阶段,每一阶段结束都有完整可运行的交付物。
第一阶段:ReactorX 事件驱动引擎(Day 1–5)
一切性能的根基。
- Day 1 实现 epoll 封装(Poller)和事件分发抽象(Channel),第一次运行时你会看到键盘输入被事件循环捕获——这是事件驱动从概念变成代码的一刻
- Day 2 构建 EventLoop,实现 One Loop Per Thread 框架,到这天结束你已经能写一个真正跑起来的 echo server
- Day 3 接入基于 timerfd 的定时器
- Day 4 实现 eventfd 跨线程唤醒和线程池
- Day 5 做压测,QPS 可达 50 万+
第二阶段:NetCore 高性能网络库(Day 6–10)
在事件驱动引擎之上,搭建完整的网络抽象层。
- Day 6 封装 Socket RAII
- Day 7 实现 Acceptor 连接接受器(含 idleFd 技巧防止 fd 耗尽)
- Day 8 实现三区域自动扩容 Buffer(readv 系统调用一次读取所有数据)
- Day 9 是整个网络库最难也最精华的一天——TcpConnection 的四状态机、
shared_ptr + weak_ptr + tie机制防止野指针和提前析构、高水位回调控制发送缓冲 - Day 10 把所有组件粘合成 Multi-Reactor 架构的 TcpServer,主 Reactor 只接连接,Sub Reactor 池负责 IO,Round-Robin 负载均衡,三十行代码启动支持数万并发的多线程服务器
第三阶段:RocketRPC 框架层(Day 11–25)
更多推荐

所有评论(0)