总结RPC远程服务调用相关的知识点

RPC远程服务调用是分布式架构的基础,无轮微服务设计上层如何发展,讨论服务治理都绕不开远程服务调用,那么如何理解RPC、有哪些常见的RPC框架,实现一款RPC框架需要哪些技术呢?

如何理解RPC

RPC(Remote Procedure Call)是一种进程间通信方式,百科给出的定义是这样的:RPC(远程过程调用协议),是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

RPC允许程序调用另一个地址空间的过程或函数,而不用程序员显式编码这个远程调用的细节,即无论是调用本地接口/服务的还是远程的接口/服务,本质上编写的调用代码基本相同。

比如两台服务器A、B,一个应用部署在A服务器上,想要调用B服务器上提供的函数或者方法,由于不在一个内存空间,则不能直接调用,这时候就可以应用RPC来解决

example

  • 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
  • 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?

首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。

客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。

数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
总结一下上述过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Client端 
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3.2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client

7632302-ca0ba3118f4ef4fb

RPC如何实现

早期的远程服务调用一般是通过RMI或Hessian等工具实现,以Java RMI为例,RMI是Java语言中RPC的一种实现方式。

Java RMI(Java 远程方法调用,Java Remote Method Invocation)是Java变成语言里,一种用于实现远程过程调用的应用程序编程接口,应用Java RMI,可以让某个Java虚拟机上的对象调用另一个Java虚拟机中的对象上的方法。

Java RMI实现主要依赖java.rmi包下面的工具类,具体流程包括继承Remote实现远程接口,开发业务逻辑,创建Server并且注册远程对象,客户端创建Client调用远程方法等,关于RMI的实现细节,由于实际开发中很少应用,这里不展开讲解了。

以Java RMI 为代表的早期RPC实现起来比较繁琐,需要在代码直接编码地址,并且不支持服务治理,比如无法对服务调用进行统计、无法梳理服务依赖情况,无法保证服务上下线是的稳定性等。随着分布式系统规模的增长,传统方式已经无法满足开发需求,于是诞生了一系列的RPC服务框架。

RPC框架代表

开源社区里有许多优秀的RPC框架,比如常用的Dubbo、Thrift、gRPC等,下面简单介绍这几款组件。

Apache Dubbo

Dubbo是阿里巴巴公司开源的一个高性能Java分布式服务框架,目前已经称为Apache顶级项目。Dubbo可以通过高性能的RPC实现服务的输出和输入。

Dubbo在设计中采用了微内核架构,基于对Java SPI 机制的扩展实现,Dubbo对分布式服务调用核心功能都开放了扩展点,包括服务调用的负载均衡策略、序列化协议、传输协议,使用者都可以添加自定义实现。

Dubbo 在国内曾经拥有很高的人气,是微服务架构的首选,后来随着 Spring Cloud 的流行,社区一度停更,外部用户发布了 DubboX 等升级版本。最近,Dubbo 社区又重新活跃,更新后的 Dubbo 也发布了 3.0 预览版等,并且宣布会在未来的版本中支持更多特性,值得期待。

Google的gRPC

gRPC是Google开发的高性能、通用的开源RPC框架,gRPC使用ProtoBuf来定义服务,ProtoBuf是Google开发的一种数据序列化协议,性能比较高,压缩和传输效率高,语法也比较简单,另外,gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。

Apache Thrift

Thrift 起源于 Facebook,和 Dubbo 一样,后来被提交 Apache 基金会将 Thrift 作为一个开源项目。Facebook 创造 Thrift 的目的是为了解决 Facebook 各系统间大数据量的传输通信,以及系统间语言环境不同需要跨平台的问题。

Thrift 支持多种编程语言,如 Java、C++、Python、PHP、Ruby 等,可以在多种不同的语言之间通信。应用 Thrift,需要在一个语言无关的 IDL 文件里,定义数据类型和服务接口,然后生成用来构建 RPC 客户和服务器所需的代码。

Thrift 主要的优点是跨语言;缺点是,由于需要定义独立的 IDL 文件,如果对服务进行修改,当数据结构发生变化时,必须重新编辑 IDL 文件、重新编译和生成相关的代码,修改起来比较繁琐。

微博 Motan

Motan 是新浪微博开源的一个 Java RPC 框架,官方文档对外宣传在微博平台已经广泛应用,每天为数百个服务完成近千亿次的调用。

Motan 基于 Java 语言开发,设计和实现与 Dubbo 比较类似,包括服务提供者(RPC Server)、服务调用方(RPC Client)、服务注册中心(Registry)三个角色。服务端会向注册中心注册服务,消费端使用服务需要先向注册中心进行订阅,根据注册中心的返回列表与具体的 服务端建立连接,进行 RPC 通讯。当服务端发生变更的时候,注册中心也会同步变更,然后同步的通知到消费端。

Motan 也提供了服务治理的功能,包括服务的发现、服务的摘除、高可用及负载均衡。

RPC框架用到哪些技术

了解了常见的RPC框架后,我们来看下实现一个RPC框架需要哪些技术。

如何建立通信

实现分布式服务框架,首先要解决不同节点之间通讯的问题,需要在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。

一般来说,建立通信可以使用成熟的网络通信框架,比如 Java 语言中的 Netty,这是一个优秀的网络通信框架。在 Dubbo、Motan 中都应用了 Netty。

如何进行网络传输

建立通信之后,节点之间数据传输采用什么协议,也就是选择什么样的二进制数据格式组织;传输的数据如何序列化和反序列化,比如在 Dubbo 中,传输协议默认使用 Dubbo 协议,序列化支持选择 Hessian、Kryo、Protobuf 等不同方式。

如何进行服务注册和被发现

服务注册和发现,也就是服务寻址,以 Dubbo 为例,下图分布式服务典型的寻址和调用过程CgqCHl6-XeSAaqlXAAE2pEPLeB0026服务注册,需要服务提供者启动后主动把服务注册到注册中心,注册中心存储了该服务的 IP、端口、调用方式(协议、序列化方式)等信息。

服务发现,当服务消费者第一次调用服务时,会通过注册中心找到相应的服务提供方地址列表,并缓存到本地,以供后续使用。当消费者再次调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从 IP 列表中取一个服务提供者调用服务。

上面列举了一些分布式服务框架的实现要点,除了这些,还有很多技术细节,比如如何实现服务调用,RPC 框架如何和服务层交互,Java 中通过代理实现服务调用,那么代理对象如何解析请求参数、如何处理返回值等。

Redis分布式锁的正确实现方式

什么是RPC?