
RPC,全称为Remote Procedure Call(远程过程调用),是一种计算机通信协议,用于允许程序在不同的计算机或网络节点上通过远程方式调用函数或方法。它允许开发者编写分布式应用程序,使得分布在不同位置的计算机能够像本地调用一样进行通信。
以下是RPC的主要特点和工作原理:
总之,RPC是一种用于构建分布式系统的通信协议,它允许应用程序在不同计算机之间进行远程调用,以实现分布式计算和协作。不同的RPC框架提供不同的功能和性能特性,开发者可以根据项目需求选择合适的RPC解决方案。
所有的 RPC 框架,它们的总体结构和实现原理都是一样的。接下来,我们以最常使用的 Spring 和 Dubbo 配合的微服务体系为例,一起来看一下,RPC 框架到底是如何实现调用远程服务的。
一般来说,我们的客户端和服务端分别是这样的:
@Component
public class HelloClient {
@Reference // dubbo注解
private HelloService helloService;
public String hello() {
return helloService.hello("World");
}
}
@Service // dubbo注解
@Component
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "Hello " + name;
}
}对于业务代码来说,无论是客户端还是服务端,除了增加了两个注解以外,和实现一个进程内调用没有任何区别。Dubbo 看起来就像把服务端进程中的实现类“映射”到了客户端进程中一样。
接下来我们一起来看一下,Dubbo 这类 RPC 框架是如何来实现调用远程服务的。
在客户端,业务代码得到的 HelloService 这个接口的实例,并不是我们在服务端提供的真正的实现类 HelloServiceImpl 的一个实例。它实际上是由 RPC 框架提供的一个代理类的实例。这个代理类有一个专属的名称,叫“桩(Stub)”
在不同的 RPC 框架中,这个桩的生成方式并不一样,
这个和编程语言的语言特性是密切相关的,所以在不同的编程语言中有不同的实现,这部分很复杂,可以先不用过多关注。我们只需要知道这个桩它做了哪些事儿就可以了。
我们知道,HelloService 的桩,同样要实现 HelloServer 接口,客户端在调用 HelloService 的 hello 方法时,实际上调用的是桩的 hello 方法,在这个桩的 hello 方法里面,它会构造一个请求,这个请求就是一段数据结构,请求中包含两个重要的信息:
然后,它会把这个请求发送给服务端,等待服务的响应。
这个时候,请求到达了服务端,然后我们来看服务端是怎么处理这个请求的
这样就完成了一次远程调用。我把这个调用过程画成一张图放在下面,可以对着这张图再消化一下上面的流程。

在上面的这个调用流程中,我们忽略了一个问题,那就是客户端是如何找到服务端地址的呢?在 RPC 框架中,这部分的实现原理其实和消息队列的实现是完全一样的,都是通过一个 NamingService 来解决的
在 RPC 框架中,这个 NamingService 一般称为注册中心。
有些 RPC 框架,比如 gRPC,是可以支持跨语言调用的。它的服务提供方和服务调用方是可以用不同的编程语言来实现的。比如,我们可以用 Python 编写客户端,用 Go 语言来编写服务端,这两种语言开发的服务端和客户端仍然可以正常通信。这种支持跨语言调用的 RPC 框架的实现原理和普通的单语言的 RPC 框架并没有什么本质的不同
再回顾一下上面那张调用的流程图,如果需要实现跨语言的调用,也就是说,图中的客户端进程和服务端进程是由两种不同的编程语言开发的。其实,只要客户端发出去的请求能被服务端正确解析,同样,服务端返回的响应,客户端也能正确解析,其他的步骤完全不用做任何改变,不就可以实现跨语言调用了吗?
在客户端和服务端,收发请求响应的工作都是 RPC 框架来实现的,所以,只要 RPC 框架保证在不同的编程语言中,使用相同的序列化协议,就可以实现跨语言的通信。
另外,为了在不同的语言中能描述相同的服务定义,也就是我们上面例子中的 HelloService 接口,跨语言的 RPC 框架还需要提供一套描述服务的语言,称为 IDL(Interface description language)。
所有的服务都需要用 IDL 定义,再由 RPC 框架转换为特定编程语言的接口或者抽象类。这样,就可以实现跨语言调用了。
讲到这里,RPC 框架的基本实现原理就很清楚了,可以看到,实现一个简单的 RPC 框架并不是很难,这里面用到的绝大部分技术, 包括:高性能网络传输、序列化和反序列化、服务路由的发现方法等,都和消息队列实现原理类似
下面就一起来实现一个“麻雀虽小但五脏俱全”的 RPC 框架。
采用 Java 语言来实现这个 RPC 框架。我们把 RPC 框架对外提供的所有服务定义在一个接口 RpcAccessPoint 中
/**
* RPC框架对外提供的服务接口
*/
public interface RpcAccessPoint extends Closeable{
/**
* 客户端获取远程服务的引用
* @param uri 远程服务地址
* @param serviceClass 服务的接口类的Class
* @param <T> 服务接口的类型
* @return 远程服务引用
*/
<T> T getRemoteService(URI uri, Class<T> serviceClass);
/**
* 服务端注册服务的实现实例
* @param service 实现实例
* @param serviceClass 服务的接口类的Class
* @param <T> 服务接口的类型
* @return 服务地址
*/
<T> URI addServiceProvider(T service, Class<T> serviceClass);
/**
* 服务端启动RPC框架,监听接口,开始提供远程服务。
* @return 服务实例,用于程序停止的时候安全关闭服务。
*/
Closeable startServer() throws Exception;
}另外,我们还需要定一个注册中心的接口 NameService:
/**
* 注册中心
*/
public interface NameService {
/**
* 注册服务
* @param serviceName 服务名称
* @param uri 服务地址
*/
void registerService(String serviceName, URI uri) throws IOException;
/**
* 查询服务地址
* @param serviceName 服务名称
* @return 服务地址
*/
URI lookupService(String serviceName) throws IOException;
}这个注册中心只有两个方法,分别是注册服务地址 registerService 和查询服务地址 lookupService。
以上,就是我们要实现的这个 RPC 框架的全部功能了。然后,我们通过一个例子看一下这个 RPC 框架如何来使用。同样,
需要先定义一个服务接口:
public interface HelloService {
String hello(String name);
}然后我们分别看一下服务端和客户端是如何使用这个 RPC 框架的
客户端
URI uri = nameService.lookupService(serviceName);
HelloService helloService = rpcAccessPoint.getRemoteService(uri, HelloService.class);
String response = helloService.hello(name);
logger.info("收到响应: {}.", response);服务端
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
String ret = "Hello, " + name;
return ret;
}
}rpcAccessPoint.startServer();
URI uri = rpcAccessPoint.addServiceProvider(helloService, HelloService.class);
nameService.registerService(serviceName, uri);可以看到,我们将要实现的这个 RPC 框架的使用方式,总体上和上面使用 Dubbo 和 Spring 的例子是一样的,唯一的一点区别是,由于我们没有使用 Spring 和注解,所以需要用代码的方式实现同样的功能。
整个项目分为如下 5 个 Module:

使用框架的例子,
在实现 RPC 框架之前,需要先掌握 RPC 框架的实现原理。
在 RPC 框架中,最关键的就是理解“桩”的实现原理,桩是 RPC 框架在客户端的服务代理,它和远程服务具有相同的方法签名,或者说是实现了相同的接口。
客户端在调用 RPC 框架提供的服务时,实际调用的就是“桩”提供的方法,在桩的实现方法中,它会发请求的服务名和参数到服务端,
服务端的 RPC 框架收到请求后,解析出服务名和参数后,调用在 RPC 框架中注册的“真正的服务提供者”,然后将结果返回给客户端