什么是RPC?

互联网上的机器大都通过相互访问,但TCP/IP只是往远端发送了一段二进制数据,为了建立服务还有很多问题需要抽象:

  • 数据以什么格式传输?不同机器间,网络间可能是不同的字节序,直接传输内存数据显然是不合适的;随着业务变化,数据字段往往要增加或删减,怎么兼容前后不同版本的格式?
  • 一个TCP连接可以被多个请求复用以减少开销么?多个请求可以同时发往一个TCP连接么?
  • 如何管理和访问很多机器?
  • 连接断开时应该干什么?
  • 万一server不发送回复怎么办?
  • RPC可以解决这些问题,它把网络交互类比为“client访问server上的函数”:client向server发送request后开始等待,直到server收到、处理、回复client后,client又再度恢复并根据response做出反应。

我们来看看上面的一些问题是如何解决的:

  • 数据需要序列化,protobuf在这方面做的不错。用户填写protobuf::Message类型的request,RPC结束后,从同为protobuf::Message类型的response中取出结果。protobuf有较好的前后兼容性,方便业务调整字段。http广泛使用作为序列化方法。
  • 用户无需关心连接如何建立,但可以选择不同的连接方式:短连接,连接池,单连接。
  • 连接断开时可以重试。
  • 如果server没有在给定时间内回复,client会返回超时错误。

哪里可以使用RPC?

RPC不是万能的抽象,否则我们也不需要TCP/IP这一层了。但是在我们绝大部分的网络交互中,RPC既能解决问题,又能隔离更底层的网络问题。

对于RPC常见的质疑有:

  • 我的数据非常大,用protobuf序列化太慢了。首先这可能是个伪命题,你得用证明慢了才是真的慢,其次很多协议支持携带二进制数据以绕过序列化。
  • 我传输的是流数据,RPC表达不了。事实上brpc中很多协议支持传递流式数据,包括http中的ProgressiveReader, h2的streams, , 和专门的流式协议RTMP。
  • 我的场景不需要回复。简单推理可知,你的场景中请求可丢可不丢,可处理也可不处理,因为client总是无法感知,你真的确认这是OK的?即使场景真的不需要,我们仍然建议用最小的结构体回复,因为这不大会是瓶颈,并且追查复杂bug时可能是很有价值的线索。

什么是?

百度内最常使用的工业级RPC框架, 有1,000,000+个实例(不包含client)和上千种服务, 在百度内叫做"baidu-rpc". 目前只开源C++版本。

  • 搭建能在一个端口支持多协议的服务, 或访问各种服务
    • restful http/https, h2/。使用brpc的http实现比libcurl方便多了。从其他语言通过HTTP/h2+json访问基于protobuf的协议.
    • memcached, 线程安全,比官方client更方便。
    • /flv/, 可用于搭建流媒体服务.
    • hadoop_rpc(可能开源)
    • 支持(即将开源)
    • 支持thrift , 线程安全,比官方client更方便
    • 各种百度内使用的协议: , streaming_rpc, hulu_pbrpc, , nova_pbrpc, public_pbrpc, ubrpc和使用nshead的各种协议.
    • 基于工业级的RAFT算法实现搭建分布式系统,已在braft开源。
  • Client支持、异步、,或使用组合channels简化复杂的分库或并发访问。
  • 调试服务, 使用cpu, , contention profilers.
  • 获得.
  • 把你组织中使用的协议快速地加入brpc,或定制各类组件, 包括 (dns, zk, etcd), 负载均衡 (rr, random, consistent hashing)

brpc的优势

只有三个(主要的)用户类: , Channel, , 分别对应server端,client端,参数集合. 你不必推敲诸如"如何初始化XXXManager", "如何组合各种组件", "XXXController的XXXContext间的关系是什么"。要做的很简单:

  • 建服务? 包含brpc/server.h并参考注释或.
  • 访问服务? 包含brpc/channel.h并参考注释或.
  • 调整参数? 看看brpc/controller.h. 注意这个类是Server和Channel共用的,分成了三段,分别标记为Client-side, Server-side和Both-side methods。 我们尝试让事情变得更加简单,以命名服务为例,在其他RPC实现中,你也许需要复制一长段晦涩的代码才可使用,而在brpc中访问BNS可以这么写"bns://node-name",DNS是,本地文件列表是"file:///home/work/server.list",相信不用解释,你也能明白这些代表什么。

brpc在百度内被广泛使用:

  • map-reduce服务和table存储
  • 高性能计算和模型训练
  • 各种索引和排序服务
  • …. 它是一个经历过考验的实现。

brpc特别重视开发和维护效率, 你可以通过浏览器或curl, 分析在线服务的cpu热点, 和锁竞争, 通过统计各种指标并通过/vars查看。

  • 对不同客户端请求的读取和解析是完全并发的,用户也不用区分”IO线程“和”处理线程"。其他实现往往会区分“IO线程”和“处理线程”,并把(对应一个客户端)散列到IO线程中去。当一个IO线程在读取其中的fd时,同一个线程中的fd都无法得到处理。当一些解析变慢时,比如特别大的protobuf message,同一个IO线程中的其他fd都遭殃了。虽然不同IO线程间的fd是并发的,但你不太可能开太多IO线程,因为这类线程的事情很少,大部分时候都是闲着的。如果有10个IO线程,一个fd能影响到的”其他fd“仍有相当大的比例(10个即10%,而工业级在线检索要求99.99%以上的可用性)。这个问题在fd没有均匀地分布在IO线程中,或在多租户(multi-tenancy)环境中会更加恶化。在brpc中,对不同fd的读取是完全并发的,对同一个fd中不同消息的解析也是并发的。解析一个特别大的protobuf message不会影响同一个客户端的其他消息,更不用提其他客户端的消息了。更多细节看这里
  • 对同一fd和不同fd的写出是高度并发的。当多个线程都要对一个fd写出时(常见于单连接),第一个线程会直接在原线程写出,其他线程会以的方式托付自己的写请求,多个线程在高度竞争下仍可以在1秒内对同一个fd写入500万个16字节的消息。更多细节看这里
  • 尽量少的锁。高QPS服务可以充分利用一台机器的CPU。比如为处理请求, 设置超时, 根据回复, 记录性能计数器都是高度并发的。即使服务的QPS超过50万,用户也很少在)中看到框架造成的锁竞争。