RPC是一种方便的网络通信编程模型,由于和编程语言的高度结合,大大减少了处理网络数据的复杂度,让代码可读性也有可观的提高。但是RPC本身的构成却比较复杂,由于受到编程语言、网络模型、使用习惯的约束,有大量的妥协和取舍之处。本文就是通过分析几种流行的RPC实现案例,提供大家在设计RPC系统时的参考。
由于RPC底层的网络开发一般和具体使用环境有关,而编程实现手段也非常多样化,但不影响使用者,因此本文基本涉及如何实现一个RPC系统。
认识 RPC (远程调用)我们在各种操作系统、编程语言生态圈中,多少都会接触过“远程调用”的概念。一般来说,他们指的是用简单的一行代码,通过网络调用另外一个计算机上的某段程序。比如:
RMI——Remote Method Invoke:调用远程的方法。“方法”一般是附属于某个对象上的,所以通常RMI指对在远程的计算机上的某个对象,进行其方法函数的调用。
RPC——Remote Procedure Call:远程过程调用。指的是对网络上另外一个计算机上的,某段特定的函数代码的调用。
远程调用本身是网络通信的一种概念,他的特点是把网络通信封装成一个类似函数的调用。网络通信在远程调用外,一般还有其他的几种概念:数据包处理、消息队列、流过滤、资源拉取等待。下面比较一下他们差异:
方案 编程方式 信息封装 传输模型 典型应用远程调用 调用函数,输入参数,获得返回值。 使用编程语言的变量、类型、函数 发出请求,获得响应 Java RMI
数据包处理 调用Send()/Recv(),使用字节码数据,编解码,处理内容 把通信内容构造成二进制的协议包 发送/接收 UDP编程
消息队列 调用Put()/Get(),使用“包”对象,处理其包含的内容 消息被封装成语言可用的对象或结构 对某队列,存入一个消息;取出一个消息 ActiveMQ
流过滤 读取一个流,或写出一个流,对流中的单元包即刻处理 单元长度很小的统一数据结构 连接;发送/接收;处理 网络视频
资源拉取 输入一个资源ID,获得资源内容 请求或响应都包含:头部+正文 请求后等待响应 WWW
针对远程调用的特点——调用函数。业界在各种语言下都开发过类似的方案,同时也有些方案是试图做到跨语言的。尽管远程调用在编程方式上,看起来似乎是最简单易用的,但是也有明显的缺点。所以了解清楚远程调用的优势和缺点,是决定是否要开发、或者使用远程调用这种模型的关键问题。
远程调用的优势有:
屏蔽了网络层。因此在传输协议和编码协议上,我们可以选择不同的方案。比如WebService方案就是用的HTTP传输协议+SOAP编码协议;而REST的方案往往使用HTTP+JSON协议。Facebook的Thrift甚至可以定制任何不同的传输协议和编码协议,你可以用TCP+Google Protocol Buffer,也可以用UDP+JSON……。由于屏蔽了网络层,你可以根据实际需要来独立的优化网络部分,而无需涉及业务逻辑的处理代码,这对于需要在各种网络环境下运行的程序来说,非常有价值。
函数映射协议。你可以直接用编程语言来书写数据结构和函数定义,取代编写大量的编码协议格式和分包处理逻辑。对于那些业务逻辑非常复杂的系统,比如网络游戏,可以节省大量定义消息格式的时间。而且函数调用模型非常容易学习,不需要学习通信协议和流程,让经验较浅的程序员也能很容易的开始使用网络编程。
远程调用的缺点:
增加了性能消耗。由于把网络通信包装成“函数”,需要大量额外的处理。比如需要预生产代码,或者使用反射机制。这些都是额外消耗CPU和内存的操作。而且为了表达复杂的数据类型,比如变长的类型string/map/list,这些都要数据包中增加更多的描述性信息,则会占用更多的网络包长度。
不必要的复杂化。如果你仅仅是为了某些特定的业务需求,比如传送一个固定的文件,那么你应该用HTTP/FTP协议模型。如果为了做监控或者IM软件,用简单的消息编码收发会更快速高效。如果是为了做代理服务器,用流式的处理会很简单。另外,如果你要做数据广播,那么消息队列会很容易做到,而远程调用这几乎无法完成。
因此,远程调用最适合的场景是:业务需求多变,网络环境多变。
RPC方案的核心问题由于远程调用的使用接口是“函数”,所以要如何构建这个“函数”,就产生了三个方面需要决策的问题:
1 . 如何表示“远程”的信息