bRPC 完整实战指南:高性能 C++ RPC 框架如何扛住长尾延迟
【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc
bRPC(better RPC)是一个工业级的 C++ RPC 框架,源自百度搜索、存储、机器学习、广告推荐等场景,在百度内部以 baidu-rpc 之名运行着 100 万个以上实例和上千种服务。它把分布式系统里一堆琐碎但棘手的问题打包解决:数据序列化与前后兼容、连接的建立复用与断线重试、从命名服务发现节点、超时与重试策略。值得投入的原因是它不满足于"单机吞吐高",而是把设计重心放在一个更现实的指标上——当 1% 的请求变慢时,剩下 99% 的请求能不能不被拖累。
一、快在哪:QPS 数字之外,看它怎么消化长尾请求
很多 RPC 框架的基准测试只比 echo 吞吐,这种测试里 brpc 反而显得"吃亏":它每个请求都会付出一次线程跳转,单次跳转代价约 3~20 微秒,还伴随独立的 L1/L2 cache 失效,变量访问从纳秒级被拉长到微秒级。brpc 在 2015 年重做了一整套基准测试(见 docs/cn/benchmark.md),规则是:
- 请求不等长,混入 1% 耗时 5 毫秒的长尾请求;
- 有 server 内嵌 client 访问下游的多级链路;
- 有单 client 打多 server、多 client 打多 server 的场景。
在这种更贴近生产环境的设定下,结果差异明显:
- 长尾隔离:固定 QPS 下 brpc 的延迟 CDF 曲线几乎没有被 5ms 长尾干扰,而对比实现中有的框架 30% 的普通请求被长尾严重拖慢,有的甚至出现一批请求直接超时。
- 全并发收包:不同连接的读取完全并发,同一连接内不同消息的解析也并发,某个超大 protobuf 消息的解析不会波及同一客户端或其他客户端的消息。
- wait-free 写出:多个线程同时向同一个 fd 写入时,第一个线程原地写,其余线程以无等待方式托付写请求,实测 1 秒内对同一 fd 写入 500 万个 16 字节消息。
- 锁极少:创建 bthread、设置定时器、按回复定位 RPC 上下文、累加性能计数器都是高并发路径,QPS 超过 50 万时,contention profiler 里几乎看不到框架自身造成的锁竞争。
图1:固定 QPS、混入 1% 长尾请求时的延迟 CDF 对比,brpc 曲线几乎不受干扰
二、一张图看懂架构:请求从 Channel 到 Service 的完整链路
brpc 的接口面很小,核心用户类只有三个:Server(服务端)、Channel(客户端)、Controller(参数集合),分别对应服务端、客户端和请求上下文,不需要理解什么 Manager、Context 的组合关系。
图2:bRPC 请求链路:Client 侧 Channel/LB 选节点后经由 Socket 收发,Server 侧由 Event Dispatcher 分发到 Parse 和 Process
把这张图拆成两侧看:
- Client 侧:Channel 持有命名服务(NamingService)和负载均衡器(LB),每次请求先选节点再落到 Socket 上;响应回来后经过 Parse、Process Response,多个 Channel 之间通过 bthread swap 相互切换,互不阻塞。
- Server 侧:Acceptor 接单后交给 Event Dispatcher,请求按"fd 之间并发 + fd 内部并发"两级并行推进,最终路由到具体的 Service。图中不同颜色代表不同线程,可以直观看到收包、解析、处理在不同线程间流动。
命名服务用字符串就能表达,写在配置文件里即可:bns://节点名、file://列表文件、list://addr1,addr2、http://域名(走 DNS)。想支持 zk、etcd 等新后端,实现一个新的 NamingService 后在 src/brpc/global.cpp 里注册即可。
三、关键设计拆解:三个"为什么"
3.1 线程模型:为什么是 M:N 的 bthread,而不是协程或裸线程
bthread 是 M:N 线程库:M 个 bthread 映射到 N 个 pthread worker,M 远大于 N,创建耗时数百纳秒。官方 FAQ 直接回答了"它是不是协程"——不是(详见 docs/cn/bthread.md)。
- 不用 N:1 协程:协程切换虽快(100~200ns),但一个协程阻塞会卡死整个 event loop 上的所有请求。百度搜索链路里几十人协作开发,一个慢函数或一次卡顿的下游访问就会放大成大规模超时。
- 不用纯 1:1 线程池:每个请求跑在独立 bthread 中,请求结束线程即销毁,线程数天然随负载自动伸缩,不用手动调节 worker 数量;同时 M 个 bthread 共享少量 pthread,thread-local cache(如 tcmalloc)的效果不被稀释。
- 隔离靠两个机制:work stealing 调度让空闲 worker 从别的 worker 那里偷任务,butex(blocking thread synchronization)让 bthread 和 pthread 可以互相等待唤醒。一个 bthread 因 bthread API 阻塞就让出 worker;因系统调用阻塞时,它名下待运行的 bthread 会被其他 worker 偷走执行。
图3:bthread worker 使用率的 bvar 监控曲线,负载上来后各 worker 使用率同步抬升
3.2 连接与并发:为什么没有"IO 线程"和"业务线程"之分
传统实现把 fd 静态散列到若干 IO 线程,一个 IO 线程解析慢消息时,它名下的其他连接全部排队。brpc 的做法是 Event Dispatcher + 全并发读写,不存在这个隔离带。
连接方式上提供三档:单连接、连接池、短连接。官方基准给出的结论很明确:
- 单连接下 brpc 吞吐超过 800MB/s,足以打满万兆网卡,且写出过程是 wait-free 的,CPU 消耗约为多连接模式的一半——真实系统优先用单连接;
- 大包场景内核单连接写入成为瓶颈时,连接池模式可以达到测试中的最高 2.3GB/s。
3.3 服务治理:命名、负载、熔断为什么做成可插拔的三件套
这三层各管一件事,且都能按字符串注册扩展:
- 命名服务负责"节点列表从哪来",支持定期拉取(bns,默认 5 秒间隔)、文件监听(file://)、内嵌列表(list://)、DNS(http://)。
- 负载均衡负责"每次挑谁",内置 round-robin、randomized、consistent-hashing(murmurhash3 或 md5)、locality-aware 四种(见 docs/cn/consistent_hashing.md 与 docs/cn/lalb.md)。
- 熔断与健康检查负责"坏的什么时候踢"。默认策略常开且不可关闭:节点返回 ECONNREFUSED、ENETUNREACH、EHOSTUNREACH、EINVAL,或连续三次连接超时,即被熔断;被熔断的连接会启动一个动态创建的 bthread 做健康检查,连上后"复活"重新参与 LB。另外可选基于出错率的激进熔断(
enable_circuit_breaker):用 EMA 平滑延迟,维护长/短两个出错窗口,适合"TCP 能连上但业务线程全卡死"这类故障节点。
四、实战避坑清单:问题 → 做法 → 收益
| 问题 | 做法 | 收益 |
|---|---|---|
| 自定义超时后故障节点永远触发不了熔断 | 保证timeout_ms > connect_timeout_ms(否则 rpc 超时先于连接超时触发,连续三次连接超时的判定永远不成立) | 故障节点能被正常剔除,避免流量持续打到坏节点 |
| 大量 bthread 调用阻塞系统调用,worker 被占满 | 调大bthread_concurrency,或在 server 端限制最大并发 | 保留足够的 worker 处理网络收发 |
| 在锁内发起 RPC,极端情况下全部 worker 阻塞在同一把 pthread mutex 上 | 养成"锁内不做 RPC"的习惯;需要兜底时启用 pthread 模式 | 避免 unlock 依赖的 RPC 回调没有线程执行,彻底死锁 |
| 线上服务状态是黑盒,靠打日志排查 | 用内置 HTTP 服务查看/connections(连接与熔断数据,含 nBreak、RecentErr 字段)、/vars(bvar 指标)、rpcz,以及 CPU、Heap、Contention 三个 profiler | 不重启、不打日志即可定位热点、内存分配和锁竞争 |
| 性能测试结论与生产表现对不上 | 测试请求加入长尾,覆盖多级 server 与多 client 场景,参考 benchmark 的测试设计 | 测试暴露的是真实瓶颈,而不是 echo 吞吐 |
图4:通过浏览器/curl 查看 server 内部状态,连接数、指标、profiler 都从这里进入
五、上手入口
克隆仓库即可开始:
git clone https://gitcode.com/GitHub_Trending/brpc/brpc建议按这个顺序读:docs/cn/getting_started.md 走通第一个 echo 服务,docs/cn/overview.md 理解 RPC 抽象与 brpc 的定位,docs/cn/bthread.md 和 docs/cn/io.md 补上线程与网络层的细节。框架层面它已经把长尾隔离、并发写出、可观测性做成了默认行为,接下来值得关注的方向是 streaming RPC、RDMA 支持,以及和 braft 组合搭建高可用分布式系统——这些在 docs 目录里都有对应的专题文档。
【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考