brpc 概览:从 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/gh_mirrors/brpc6/brpc
brpc 是一个使用 C++ 编写的工业级 RPC 框架,常用于搜索、存储、机器学习、广告、推荐等高并发系统中,"brpc" 意为 "better RPC"。本文以仓库中的 docs/cn/overview.md 为骨架,系统讲解 RPC 要解决的根本问题、brpc 的适用范围与多协议能力,并结合仓库源码与示例(如 echo_c++)剖析其"三个用户类""单端口多协议""高并发低锁竞争"等核心设计,帮助读者快速建立对 brpc 的完整认知,并能在实际项目中正确选型与上手。
什么是 RPC
互联网上的机器大都通过 TCP/IP 协议相互访问,但 TCP/IP 只是向远端发送了一段二进制数据。要真正搭建起服务,还必须抽象并解决一系列问题:
- 数据格式:数据以什么格式传输?不同机器、不同网络之间可能存在不同的字节序,直接传输内存数据显然不合适;随着业务变化,数据字段往往要增加或删减,如何兼容前后不同版本的格式?
- 连接复用:一个 TCP 连接可以被多个请求复用以减少开销吗?多个请求可以同时发往一个 TCP 连接吗?
- 机器管理:如何管理和访问很多机器?
- 故障处理:连接断开时应该做什么?万一 server 不发送回复怎么办?
RPC(Remote Procedure Call,远程过程调用)正是为了解决这些问题而生的抽象:它把网络交互类比为"client 访问 server 上的函数"。client 向 server 发送 request 后开始等待,直到 server 收到、处理、回复 client 后,client 才再度恢复,并根据 response 做出反应。
RPC 如何解决上述问题
- 数据序列化:protobuf 在这方面做得很不错。用户填写
protobuf::Message类型的 request,RPC 结束后从同为protobuf::Message类型的 response 中取出结果。protobuf 具备较好的前后兼容性,方便业务调整字段。HTTP 生态则广泛使用 json 作为序列化方法。 - 连接方式:用户无需关心连接如何建立,但可以选择不同的连接方式:短连接、连接池、单连接。
- 服务发现与负载均衡:大量机器一般通过命名服务被发现,可基于 DNS、ZooKeeper、etcd 等实现。在百度内部使用 BNS(Baidu Naming Service);brpc 也提供
list://和file://两种命名服务。用户可以指定负载均衡算法,让 RPC 每次选出一台机器发送请求,包括 round-robin、randomized、consistent-hashing(murmurhash3 或 md5)以及 locality-aware。 - 故障恢复:连接断开时可以重试;如果 server 没有在给定时间内回复,client 会返回超时错误。
哪里可以使用 RPC
几乎所有的网络交互都可以使用 RPC。当然,RPC 不是万能的抽象,否则我们也不需要 TCP/IP 这一层了;但在绝大部分网络交互场景中,RPC 既能解决问题,又能隔离更底层的网络问题。
对 RPC 常见的质疑,brpc 也给出了务实回应:
- "我的数据非常大,用 protobuf 序列化太慢了":首先这可能是个伪命题,你得用 profiler 证明慢了才是真的慢;其次,很多协议支持携带二进制数据以绕过序列化。
- "我传输的是流数据,RPC 表达不了":事实上 brpc 中很多协议支持传递流式数据,包括 http 中的 ProgressiveReader、h2 的 streams、streaming rpc,以及专门的流式协议 RTMP。
- "我的场景不需要回复":简单推理可知,这种场景中请求可丢可不丢、可处理也可不处理,因为 client 总是无法感知,你真的确认这是 OK 的吗?即使场景真的不需要,仍然建议用最小的结构体回复——这不大会是瓶颈,而且可能是追查复杂 bug 时很有价值的线索。
什么是 brpc
brpc 是百度内部最常使用的工业级 RPC 框架,有 1,000,000+ 个实例(不包含 client)和上千种服务,在百度内部称为 "baidu-rpc",目前只开源 C++ 版本(该数据出自 docs/cn/overview.md,为百度内部场景事实)。
单端口支持多协议
brpc 可以搭建在一个端口上支持多协议的服务,或访问各种服务:
- HTTP 系:restful http/https、h2、gRPC。使用 brpc 的 http 实现比 libcurl 方便得多,可让其他语言通过 HTTP/h2+json 访问基于 protobuf 的协议。
- 缓存/存储协议:redis 和 memcached 客户端,线程安全,比官方 client 更方便。
- 流媒体协议:rtmp、flv、hls,可用于搭建流媒体服务。
- 跨语言协议:支持 thrift,线程安全,比官方 client 更方便。
- 各类内部协议:各种百度内使用的协议,包括 baidu_std、streaming_rpc、hulu_pbrpc、sofa_pbrpc、nova_pbrpc、public_pbrpc、ubrpc,以及使用 nshead 的各种协议。
- 分布式一致性:基于工业级 RAFT 算法实现搭建高可用分布式系统,已在 braft 开源。
Server 与 Client 的多种调用模式
- Server 可以同步或异步处理请求。
- Client 支持同步、异步、半同步,或使用组合 channels 简化复杂的分库或并发访问。
调试、性能剖析与扩展
- 通过 http 界面调试服务,使用 cpu、heap、contention profilers。
- 获得更好的延时和吞吐。
- 把你组织中使用的协议快速加入 brpc,或定制各类组件,包括命名服务(dns、zk、etcd)、负载均衡(rr、random、consistent hashing)。
brpc 的优势
更友好的接口:只有三个主要的用户类
brpc 只暴露三个主要用户类:Server、Channel、Controller,分别对应 server 端、client 端和参数集合。你无需推敲"如何初始化 XXXManager""如何组合各种组件""XXXController 的 XXXContext 间的关系是什么"之类的问题。从源码结构可以印证这一点:
class Server(src/brpc/server.h)承担服务端的注册与启动,提供了多个Start()重载,例如 Start(const char* ip_port_str, ...)、Start(const butil::EndPoint&, ...)、Start(int port, ...) 等。class Channel : public ChannelBase(src/brpc/channel.h)代表到 Server 的一条通信线路,且是线程安全的,可被程序内所有线程共享。Controller继承自google::protobuf::RpcController(src/brpc/controller.h),是 Server 和 Channel 共用的参数集合,其方法明确分成了三段:Client-side methods(controller.h)、Server-side methods(controller.h)和 Both-side methods(controller.h)。
要做的事情很简单:
- 建服务?包含
brpc/server.h并参考注释或 echo 示例的 server.cpp:server.AddService(...)注册服务,server.Start(...)启动监听,server.RunUntilAskedToQuit()等待退出。 - 访问服务?包含
brpc/channel.h并参考注释或 echo 示例的 client.cpp:channel.Init(...)初始化,再构造 stub 发起调用。 - 调整参数?看看
brpc/controller.h,注意这个类是 Server 和 Channel 共用的,分成了三段,分别标记为 Client-side、Server-side 和 Both-side methods。
brpc 还尝试让事情更加简单。以命名服务为例,在其他 RPC 实现中,你也许需要复制一长段晦涩的代码才可使用,而在 brpc 中访问 BNS 可以这么写"bns://node-name",DNS 是"http://domain-name",本地文件列表是"file:///home/work/server.list"——不用解释,你也能明白这些代表什么。
快速上手:echo 示例的 server 与 client
仓库中的 example/echo_c++ 是理解这三个类的最短路径。server 侧(server.cpp)展示了:
- 用 gflags 定义
port、listen_addr、idle_timeout_s等启动参数; - 实现
EchoService的Echo方法,通过brpc::ClosureGuard以 RAII 风格保证done->Run()被调用,通过cntl->request_attachment()/cntl->response_attachment()演示"绕过序列化直接透传二进制附件"的用法; server.AddService(&echo_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE)注册服务,server.Start(point, &options)启动。
client 侧(client.cpp)展示了:
- 通过
brpc::ChannelOptions设置protocol、connection_type、timeout_ms、max_retry等参数; channel.Init(server, load_balancer, &options)初始化 channel;- 构造
EchoService_Stub后同步调用stub.Echo(&cntl, &request, &response, NULL),并通过cntl.Failed()、cntl.ErrorText()、cntl.latency_us()判断结果与统计时延。
编译运行方式可参考 docs/cn/getting_started.md:使用sh config_brpc.sh+make,或cmake -B build && cmake --build build构建后,进入example/echo_c++执行./echo_server与./echo_client。
使服务更加可靠
brpc 在百度内被广泛使用:map-reduce 服务和 table 存储、高性能计算和模型训练、各种索引和排序服务……它是一个经历过考验的实现。
brpc 特别重视开发和维护效率:你可以通过浏览器或 curl 查看 server 内部状态,分析在线服务的 cpu 热点、内存分配 和锁竞争,通过 bvar 统计各种指标并通过 /vars 查看。
更好的延时和吞吐
虽然大部分 RPC 实现都声称"高性能",但数字仅仅是数字,要在广泛的场景中做到高性能仍是困难的。为了统一百度内的通信架构,brpc 在性能方面比其他 RPC 走得更深。原文档从四个维度阐述了其设计:
- 对不同客户端请求的读取和解析完全并发。用户不用区分"IO 线程"和"处理线程"。其他实现往往会区分二者,并把 fd(对应一个客户端)散列到 IO 线程中去:当一个 IO 线程在读取其中的 fd 时,同一个线程中的 fd 都无法得到处理;当一些解析变慢时(比如特别大的 protobuf message),同一个 IO 线程中的其他 fd 都遭殃了。虽然不同 IO 线程间的 fd 是并发的,但 IO 线程数量通常有限,一个 fd 能影响到的"其他 fd"仍有相当大的比例(10 个 IO 线程即 10%,而工业级在线检索要求 99.99% 以上的可用性);这个问题在 fd 没有均匀分布,或多租户(multi-tenancy)环境中会更加恶化。在 brpc 中,对不同 fd 的读取是完全并发的,对同一个 fd 中不同消息的解析也是并发的——解析一个特别大的 protobuf message 不会影响同一个客户端的其他消息,更不用提其他客户端的消息了。更多细节见 docs/cn/io.md#收消息。
- 对同一 fd 和不同 fd 的写出高度并发。当多个线程都要对一个 fd 写出时(常见于单连接),第一个线程会直接在原线程写出,其他线程会以 wait-free 的方式托付自己的写请求。多个线程在高度竞争下仍可以在 1 秒内对同一个 fd 写入 500 万个 16 字节的消息。更多细节见 docs/cn/io.md#发消息。
- 尽量少的锁。高 QPS 服务可以充分利用一台机器的 CPU。比如为处理请求创建 bthread、设置超时、根据回复找到 RPC 上下文、记录性能计数器 都是高度并发的。即使服务的 QPS 超过 50 万,用户也很少在 contention profiler 中看到框架造成的锁竞争。
- 服务器线程数自动调节。传统的服务器需要根据下游延时调整自身的线程数,否则吞吐可能会受影响。在 brpc 中,每个请求均运行在新建立的 bthread 中,请求结束后线程就结束了,所以天然会根据负载自动调节线程数。
brpc 和其他实现的性能对比见 docs/cn/benchmark.md。
小结
本文从 RPC 要解决的根本问题出发,完整介绍了 brpc 的定位、适用范围、多协议能力、三个核心用户类与性能设计。整体上,brpc 的设计可以概括为三点:接口极简(Server/Channel/Controller 三个类覆盖全部使用场景)、能力内聚(单端口多协议、服务发现、负载均衡、内置调试与 profiling 开箱即用)、并发深入(读写全并发、低锁竞争、bthread 自动调节线程数)。对希望在一个端口上承接多协议、或需要极致并发性能的 C++ 服务而言,docs/cn/overview.md 及其链接的 docs/cn/client.md、docs/cn/server.md、docs/cn/io.md、docs/cn/bthread.md 等文档是继续深入的最佳入口,example/echo_c++ 则是可以立刻运行的实战起点。
【免费下载链接】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/gh_mirrors/brpc6/brpc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考