1.为什么需要RPC
为什么需要 RPC 呢?通俗的讲就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,
比如不同的系统间的通讯,甚至不同的组织间的通讯。
由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用,
2.RPC(远程过程调用)是什么
简单的说,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯)
RPC 是一个请求响应模型。客户端发起请求,服务器返回响应(类似于Http的工作方式)
RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。
远程过程调用发展历程
2.1早期成熟的 RPC
ONC RPC (开放网络计算的远程过程调用),
OSF RPC(开放软件基金会的远程过程调用)
CORBA(Common Object Request Broker Architecture公共对象请求代理体系结构)
DCOM(分布式组件对象模型),COM+
NET Remoting
XML-RPC,SOAP,Web Service
PHPRPC,Hessian,JSON-RPC
Microsoft WCF,WebAPI
ZeroC Ice,Thrift,GRPC
Hprose
2.2 Java RMI
我们着重,聊一下Java RMI 通过创建存根函数来工作。存根由 rmic 编译器生成。自 Java 1.5 以来,Java 支持在运行时动态生成存根类。编译器 rmic 会提供各种编译选项。
RMI 架构
RMI 是一个三层架构。最上面是 Stub/Skeleton layer(存根/骨架层)。方法调用从 Stub、Remote Reference Layer (远程引用层)和 Transport Layer(传输层)向下,传递给主机,然后再次经传 Transport Layer 层,向上穿过 Remote Reference Layer 和 Skeleton ,到达服务器对象。 Stub 扮演着远程服务器对象的代理的角色,使该对象可被客户激活。Remote Reference Layer 处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。Transport Layer 管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的 Skeleton 完成对服务器对象实际的方法调用,并获取返回值。返回值向下经 Remote Reference Layer 、服务器端的 Transport Layer 传递回客户端,再向上经 Transport Layer 和 Remote Reference Layer 返回。最后,Stub 程序获得返回值。
BankAccount acct = new BankAcctImpl(); String url = "rmi://java.sun.com/account"; // bind url to remote object java.rmi.Naming.bind(url, acct); // look up account acct = (BankAccount)java.rmi.Naming.lookup(url);
2.3早期的 RPC历史发展进程
第一代 RPC(ONC RPC,OSF RPC)不支持对象的传递。
CORBA 太复杂,各种不同实现不兼容,一般程序员也玩不转。
DCOM,COM+ 逃不出 Windows 的手掌心。
RMI 只能在 Java 里面玩。NET Remoting 只能在 .NET 平台上玩。XML-RPC,SOAP,WebService冗余数据太多,处理速度太慢。RPC 风格的 Web Service 跨语言性不佳,而 Document 风格的 Web Service 又太过难用。
Web Service 没有解决用户的真正问题,只是把一个问题变成了另一个问题。
Web Service 的规范太过复杂,以至于在 .NET 和 Java 平台以外没有真正好用的实现,甚至没有可用的实现。
跨语言跨平台只是 Web Service 的一个口号,虽然很多人迷信这一点,但事实上它并没有真正实现。
2.3.1 PHPRPC
基于 PHP 内置的序列化格式,在跨语言的类型映射上存在硬伤。
通讯上依赖于 HTTP 协议,没有其它底层通讯方式的选择。
内置的加密传输既是特点,也是缺点。
虽然比基于 XML 的 RPC 速度快,但还不是足够快。
2.3.2 Hessian
二进制的数据格式完全不具有可读性。
官方只提供了两个半语言的实现(Java,ActionScript 和不怎么完美的 Python 实现),其它语言的第三方实现良莠不齐。
支持的语言不够多,对 Web 前端的 JavaScript 完全无视。
虽然是动态 RPC,但动态性仍然欠佳。
虽然比基于 XML 的 RPC 速度快,但还不是足够快。
2.3.3 JSON-RPC
JSON 具有文本可读性,且比 XML 更简洁。
JSON 受 JavaScript 语言子集的限制,可表示的数据类型不够多。
JSON 格式无法表示数据内的自引用,互引用和循环引用。
某些语言具有多种版本的实现,但在类型影射上没有统一标准,存在兼容性问题。
JSON-RPC 虽然有规范,但是却没有统一的实现。在不同语言中的各自实现存在兼容性问题,无法真正互通。
2.3.4 Microsoft WCF,WebAPI
它们是微软对已有技术的一个 .NET 平台上的统一封装,是对 .NET Remoting、WebService 和基于 JSON 、XML 等数据格式的 REST 风格的服务等技术的一个整合。虽然号称可以在 .NET 平台以外来调用它的这些服务,但实际上跟在 .NET 平台内调用完全是两码事。它没有提供任何在其他平台的语言中可以使用的任何工具。
3. 如何选型RPC框架
我们需要考虑如下这些问题
3.1如何表示数据
在本地系统上不存在数据不相容的问题,因为数据格式总是相同的。而在分布式系统中,不同远程机器上可能有不同的字节顺序,不同大小的整数,以及不同的浮点表示。对于 RPC,如果想与异构系统通信,我们就需要想出一个“标准”来对所有数据类型进行编码,并可以作为参数传递。例如,ONC RPC 使用 XDR (eXternal Data Representation) 格式 。这些数据表示格式可以使用隐式或显式类型。隐式类型,是指只传递值,而不传递变量的名称或类型。常见的例子是 ONC RPC 的 XDR 和 DCE RPC 的 NDR。显式类型,指需要传递每个字段的类型以及值。常见的例子是 ISO 标准 ASN.1 (Abstract Syntax Notation)、JSON (JavaScript Object Notation)、Google Protocol Buffers、以及各种基于 XML 的数据表示格式。
3.2 如何选用传输协议
有些实现只允许使用一个协议(例如 TCP )。大多数 RPC 实现支持几个,并允许用户选择。
3.3 出错时,会发生什么
相比于本地过程调用,远程过程调用出错的机会将会更多。由于本地过程调用没有过程调用失败的概念,项目使用远程过程调用必须准备测试远程过程调用的失败或捕获异常。
3.4 远程调用的语义是什么
调用一个普通的过程语义很简单:当我们调用时,过程被执行。远程过程完全一次性调用成功是非常难以实现。执行远程过程可以有如下结果:
如果服务器崩溃或进程在运行服务器代码之前就死了,那么远程过程会被执行0次;
如果一切工作正常,远程过程会被执行1次;
如果服务器返回服务器存根后在发送响应前就奔溃了,远程过程会被执行1次或者多次。客户端接收不到返回的响应,可以决定再试一次,因此出现多次执行函数。如果没有再试一次,函数执行一次;
如果客户机超时和重新传输,那么远程过程会被执行多次。也有可能是原始请求延迟了。两者都可能会执行或不执行。
RPC 系统通常会提供至少一次或最多一次的语义,或者在两者之间选择。如果需要了解应用程序的性质和远程过程的功能是否安全,可以通过多次调用同一个函数来验证。如果一个函数可以运行任何次数而不影响结果,这是幂等(idempotent)函数的,如每天的时间、数学函数、读取静态数据等。否则,它是一个非幂等(nonidempotent)函数,如添加或修改一个文件)。
3.5 远程调用的性能怎么样
毫无疑问,一个远程过程调用将会比常规的本地过程调用慢得多,因为产生了额外的步骤以及网络传输本身存在延迟。然而,这并不应该阻止我们使用远程过程调用。
3.6 远程调用安全吗?
使用 RPC,我们必须关注各种安全问题:
客户端发送消息到远程过程,那个过程是可信的吗?
客户端发送消息到远程计算机,那个远程机器是可信的吗?
服务器如何验证接收的消息是来自合法的客户端吗?服务器如何识别客户端?
消息在网络中传播如何防止时被其他进程嗅探?
可以由其他进程消息被拦截和修改时遍历网络从客户端到服务器或服务器端?
协议能防止重播攻击吗?
如何防止消息在网络传播中被意外损坏或截断?
3.7 远程过程调用的优点
你不必担心传输地址问题。服务器可以绑定到任何可用的端口,然后用 RPC 名称服务来注册端口。客户端将通过该名称服务来找到对应的端口号所需要的程序。而这一切对于程序员来说是透明的。
系统可以独立于传输提供者。自动生成服务器存根使其可以在系统上的任何一个传输提供者上可用,包括 TCP 和 UDP,而这些,客户端可以动态选择的。当代码发送以后,接收消息是自动生成的,而不需要额外的编程代码。
应用程序在客户端只需要知道一个传输地址——名称服务,负责告诉应用程序去哪里连接服务器函数集。
使用函数调用模型来代替 socket 的发送/接收(读/写)接口。用户不需要处理参数的解析。
4. 其他常见RPC框架
Dubbo
Dubbo是阿里巴巴开源的高性能RPC框架,它支持多种协议和序列化方式,并提供了丰富的特性,例如负载均衡、集群容错、服务治理等。
gRPC
gRPC是Google开源的高性能RPC框架,它使用Protocol Buffers作为默认的序列化协议,支持多种语言和平台,并提供了基于HTTP/2协议的双向流式通信。
Thrift
Thrift是Facebook开源的跨语言RPC框架,它支持多种语言和平台,并提供了多种协议和序列化方式,包括二进制、压缩、JSON等。
Apache Axis
Apache Axis是Apache基金会的开源RPC框架,它提供了基于SOAP协议的Web服务和基于REST协议的Web服务。
Apache DubboX
Apache DubboX是Apache基金会的开源RPC框架,它是Dubbo框架的增强版,提供了更加灵活的扩展机制和更加丰富的特性。
JBoss Remoting
JBoss Remoting 是 JBoss 社区开发的一种 RPC 框架,它支持多种传输协议和数据格式,例如 TCP、UDP、HTTP、SOAP 等。
Apache Avro
Apache Avro 是 Apache 基金会的开源 RPC 框架,它使用二进制协议传输数据,并支持多种语言和平台。