机器之间如何高效交流?90后技术专家带你读懂RPC框架

OB君:在单机环境下,进程之间的通信主要经过IPC来实现。可是在不一样的机器上,RPC就成为了进行通信的经典方式。本文由OceanBase的90后技术专家符风向你们娓娓道来RPC框架背后的What、Why和How,并对目前主流的C++ RPC框架进行对比分析。

本文做者:符风
现任蚂蚁金服OceanBase团队技术专家,2012年毕业后加入Oceanbase团队,主要负责OceanBase基础库的建设工做。程序员

早期的计算机程序都是单机程序,一个程序只会在一台机器上跑。若是一台机器上的A进程和B进程,它们之间须要通信交流,就只能经过IPC(Inter-Process Communication)的方式。典型的就有管道、信号量、共享内存等方式。可是若是这两个进程分别运行在不一样的机器上,那么使用IPC就不够了,还须要把网络通信这个功能加进来。其中,RPC就是在不一样机器上进行通信的经典方式。数据库

什么是RPC?

说白了,RPC就是一种网络上程序之间的沟通方式。稍微正式一点的定义咱们能够参考维基百科上查询到的结果:编程

RPC(Remote Procedure Call)或者叫作远程过程调用是一个计算机通讯协议。该协议容许运行于一台计算机的程序调用另外一台计算机的程序,而程序员无需额外地为这个交互做用编程。
—— 维基百科

从维基百科的介绍中,咱们能够总结出如下几个关键字:通讯协议,跨计算机调用,易编程数组

  • RPC必需要有本身的通信协议,协议包括序列化协议和网络协议,典型的有JSON、XML等等
  • 由于要跨计算机调用,因此RPC必需要有本身的网络传输层,典型的有TCP/IP等等
  • RPC都是封装成函数调用的方式,使用RPC时就像调用一个函数那样就能够了

为何用RPC?

网络上进程进行通信的方式有不少,好比像咱们浏览网页使用的HTTP协议,为何不是直接发个HTTP请求进行通信呢?使用RPC进行封装的通信方式有什么优点呢?最大的好处就是易用性安全

若是没有RPC的封装,一台计算机上的进程要访问另一台机器上进程的内容须要怎么作呢?bash

1. 以一种对方可以理解的协议构建请求包
2. 将这个请求包设法发送到对方机器的进程上
3. 对方机器根据协议理解请求包的内容
4. 对方机器处理请求,并以客户机可以理解的协议构造回复包
5. 想办法把这个回复包发送到请求包来源的远程机器上的具体进程,并设法把请求包和回复包关联起来

咱们能够看到,这是一个比较复杂而且烦人的工做,单单是若是把消息从一台机器发送到另一台机器就涉及不少问题:好比怎么进行传输、如何通知进程等等。况且还要处理网络上的各类异常状况:超时、断连接、网络抖动等。微信

若是使用RPC进行通信须要几个步骤的?最简单的状况就是直接调用相应函数就能够了,由RPC框架把上面的这五部操做都完成了。网络

List employees;
RPC.getEmployeeList(dep_id, employees);
复制代码

是否是感受这个世界都变得清爽了?数据结构

典型的RPC框架介于传输层和应用中间,它会帮助处理:框架

  1. 可靠性
    好比传输层遇到的错误。
  2. 平台无关性
    好比Windows平台是否能够和Linux平台进行通信?64位的系统是否能够和32位系统进行通信?
  3. 服务发现和路由选择
    RPC调用其实是对某个服务的调用,那么RPC框架须要解决具体调用须要落到哪台机器的哪一个进程上。
  4. 消息分发
    通常一个进程上会提供多种RPC调用,RPC框架须要提供区别不一样类型RPC消息并转到相应处理函数上。
  5. 安全性

RPC框架的设计

RPC框架的核心部分有如下几个:

  1. RPC接口
  2. 对象序列化和反序列化
  3. 传输协议和传输层
  4. RPC消息分发

RPC接口

RPC的做用就是让使用者调用远程请求就像调用本地函数,因此不论是本地客户端仍是远程服务端须要使用一套统一的接口,而后两边分别实现本身的逻辑。

举个例子:

好比在设计一个获取部门员工列表的RPC请求,接口可能以下设计,传入一个部门ID,返回部门成员的列表:

void getEmployeeList(const DepartmentID &id, EmployeeList &list);
复制代码

而后在客户端部分根据接口能够这样实现:

// clientvoid getEmployeeList(const DepartmentID &id, EmployeeList &list){
    auto result = Transport.send(GET_EMPLOYEE_LIST, encode(id));
    list.decode(result.buffer());}
复制代码

服务端部分一样根据接口实现具体逻辑:

// servervoid getEmployeeList(const DepartmentID &id, EmployeeList &list){
    auto dep = department_map[id];
    list = dep.get_employees();}
复制代码

接口的做用就是:告诉客户端你只能这样调用这个RPC,同时告诉服务端客户端那边只会这样调用。

上面三段代码中,具体RPC实现者只须要关心接口和服务端的实现逻辑。因此通常RPC框架会提供友好的封装,最简单的形式就是只须要实现一个函数,函数的签名就是接口,实现就是服务端的代码,客户端的代码则由框架自动填充。

对象序列化和反序列化

对象序列化的方式有不少种,好比比较通用的JSON、Protocol Buffers等,也能够按照本身的需求本身定制序列化和反序列化方式。

OceanBase就是设计了本身的对象序列化方式,以知足使用过程当中对高性能、少资源占用和易用性各方面的权衡。它相比protobuf能够拥有更高的序列化性能和更小的空间占用,而且和OceanBase的数据结构深度结合,不用像protobuf那样使用本身的DSL语言定义数据类型。

传输协议和传输层

RPC要实现网络上的两台计算机间的通信,必须依赖具体的网络传输层才能完成。

传输层的选择也有不少,用得比较多的好比TCP、UDP这类的,也有为了追求性能选择RDMA/RoCE/DPDK,或者像gRPC那样选择更上层的HTTP2。

传输层的选择须要考虑几个因素:

  • 物理限制,有些协议须要在特定的硬件环境中才能运行
  • 传输特性,好比TCP协议对可靠性有必定保证,可是须要用户本身处理黏包问题
  • 性能、安全性、是否好调试等因素也能够进行参考

OceanBase的RPC框架使用的传输层主要是基于TCP协议的封装。咱们没有选择UDP是为了简化传输层的逻辑,TCP相比UDP而言拥有更完善的控制能力,框架不须要再为拆包组包这些逻辑编写额外的代码。而且,咱们为TCP封装了连接复用的功能,避免由于网络延迟等缘由须要建立过多的TCP链接。固然咱们目前正在探索更多的协议加入至传输层,以知足不一样场景下的传输需求。

RPC消息分发

当服务端收到一个RPC消息后,须要根据RPC类型和相应规则分发请求到相应的处理函数上。常见的作法有:

  1. 暴力switch/if else,由编译器作这方面的优化
  2. 实现查找表,以RPC类型(常见为枚举类型)做为下标定位处处理函数
  3. hashmap

其中,使用hashmap的方式最为灵活。前两种方案的实现都有必定的局限性。

OceanBase早期用的是if else分支判断:若是RPC请求包是该类型的,就选择这个处理函数,不然选择另外的处理函数。原先这样作的是由于简单易实现,而且当时请求的种类也不多,效率上并无太大差异。

后来随着请求种类的增长,使用分支判断风格分发请求在性能上的劣势就慢慢凸显出来了,咱们就改用了使用RPC Code做为索引到数组中去查找对应的处理函数。使用查找表最大的问题是若是RPC Code跨度很大,就须要一个很是大的数组来保存这个隐射关系,它会使用更多的内存以及内存访问的局部性变差。Hashmap是更通用一些的方式,后续OceanBase会考虑迁移到这个方案上。

主流C++ RPC框架对比

gRPC

gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发。它支持众多开发语言,固然也包含了C++了。

gRPC最大的优点是由于IDL基于ProtoBuf的缘故因此很是简单易用,生成的代码也比较小巧易懂,文档很是健全。缺点则是传输层绑定了HTTP/2,序列化层绑定了ProtoBuf,二者都不支持动态定制。HTTP/2对于分布式数据库场景下的对性能有极致追求的场景不太友好。

Thrift

Thrift是一种接口描述语言和二进制通信协议,它被用来定义和建立跨语言的服务。它被看成一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的,目前由Apache基金会管理。

Thrift最大的优点是灵活性很是高,每一层均可以让用户作出不一样的选择从而构建出合适本身场景的RPC需求。各层基本均可以用户本身进行定制,好比用户自定义的协议、传输方式和路由分发规则等。这个经过官网的模块图就能够看出来:

PhxRPC

PhxRPC是微信后台团队推出的一个很是简洁小巧的RPC框架,编译生成的库文件很是小巧。根据官网的介绍,它使用ProtoBuf做为IDL,而且只支持ProtoBuf。使用半同步半异步模式,也就是说有专门的IO线程处理epoll。支持ucontext和过载保护。缺点是功能简单,文档也比较少。

brpc

brpc是百度开源的RPC框架。它是一套比较完整的RPC框架,和Thrift一个量级。brpc使用ProtoBuf做为IDL,也可使用Thrift工具生成代码整合至brpc后和Thrift服务进行通信。brpc也是一套很是灵活的框架,支持各层的自定义控制;而且从一些网站的介绍和对比测试来看,它的性能也颇有竞争力。

总结

RPC是实现网络间不一样计算进行通信的手段,它很好的屏蔽了网络层的细节,让用户跨计算机访问就像调用本地函数那样方便。可是它并不适用于全部的通信场景,好比作大数据传输时可能直接使用传输层的API更加方便直观,再好比它在处理服务端消息推送的时候会比较麻烦。

实现RPC框架也须要权衡不少因素,好比安全性和通信效率的平衡、异常处理机制的完善程度等等。

不管如何,一个好用的RPC框架在绝大多数的分布式系统中都扮演着很是重要的角色。

符风邀请你加入OceanBase技术交流群

想跟本文做者 符风 深刻交流吗?

想认识蚂蚁金服OceanBase的一线技术专家吗?

扫描下方二维码联系蚂蚁金服加群小助手,快速加入OceanBase技术交流群!

相关文章
相关标签/搜索