什么是RPC ?
RPC(Remote Procedure Call)叫做远程过程调用。它是利用网络从远程计算机上请求服务,可以理解为把程序的一部分放到其他远程计算机上执行。通过网络通信将调用请求发送到远程计算机后,利用远程计算机上的执行这部分程序
- 远程过程 : 远程过程是相对于本地过程而言的,本地过程也可以认为是本地函数调用,发起调用的方法和被调用的方法都在同一个地址空间或者内存空间内。而远程过程是指把进程内的部分程序逻辑放到其他机器上,也就是现在常说的业务拆解,让每个服务仅对单个业务负责,让每个服务具备独立的可扩展性,可升级性,易维护。在每台机器上提供的服务被称为远程过程,这个概念使正确地构建分布式计算更加容易,也为后续的服务化架构风格奠定了基础
- 过程调用:这个概念非常通俗易懂,它包含我们平时见到的方法调用,函数调用,并且用于程序的控制和传输。而当“过程调用”遇到“远程过程”时,意味着过程调用可以跨越及其,网络进行程序的控制和数据的传输。
RPC核心组成部分
user(服务调用方)
也叫服务消费者(Consumer),它的职责之一是提供需要调用的接口的全限定名和方法,调用方法的参数给调用方的本地存根,职责之二是从调用方的本地存根中接收执行结果。
user-stub (调用方的本地存根)
服务调用方的本地存根与服务消费者都属于Consumer端,它们存在于同一台机器上,服务调用方的本地存根会接收Consumer的函数调用,本地存根会解析函数调用的函数名,参数等信息,整理并且组装这些数据,然后将这些数据按照定义好的协议进行序列化,打包成可传输的消息,交给RPCRuntime。服务调用方的本地存根除了会处理服务消费者提供的方法,参数,方法参数类型等数据,还会处理服务提供方返回的结果,它会将RPCRuntime返回的数据包反序列化成服务调用方所需要的数据结果并传递给服务消费方。
PRC Runtime (RPC通信者)
RPC依赖于互联网,远程过程调用的本质就是远程通信,所以RPC必不可缺的就是通信者。
RPC协议用于负责数据包的重传,确认,路由和加密。
server-stub (服务端的本地存根)
服务提供方的本地存根会重新转换客户端传递的数据,以便在Provider端的机器上找到对应的函数,传递正确的参数数据,最终正确地执行真是函数的调用。等函数执行完成后,服务提供方会将执行结果返回给服务提供方的本地存根,由本地存根再将结果数据序列化,打包,最后交给RPCRuntime。
server (服务端)
服务提供方(Provider) 就是上述提到的服务端,它的职责就是提供服务,执行接口实现的方法逻辑,也就是为服务提供方的本地存根提供方法的具体实现。
调用过程
服务暴露
进程准备好所有服务并完成端口的绑定和监听后,就完成了服务暴露的过程。但是当服务器进行了切换后,调用方的调用就会失败,因为 四元组发生了变化,所以需要将服务暴露到远程。
远程服务暴露就是用一个统一的管理中心来管理应用服务的地址和服务信息。这个统一的管理中心就是注册中心,,应用服务暴露到远程的第一步也是在本地绑定端口。绑定完成后还要讲Provider端的应用服务信息注册到注册中心。并与注册中心保持心跳保活,当保活失败时,将服务从注册中心移除。
服务发现
Consumer端开启服务发现过程。需要直到服务提供者的地址和端口
直连式服务发现
消费者根据服务暴露的地址和端口直接连接远程服务。
注册中心式服务发现
消费者通过注册中心进行服务发现,也就是服务提供者的地址和端口从注册中心获取,当服务提供者变化时,注册中心能够通知服务消费者有关服务提供者的变化。
服务引用
服务引用的过程发生在服务发现之后,当消费端通过服务发现获取所有服务提供者的地址后,通过负载均衡策略选择其中一个服务提供者的节点进行服务引用,服务引用的过程就是与某一个服务节点建立连接,以及在消费端创建接口的代理的过程。
方法调用
服务引用完成后,消费端和服务端建立了连接,可以进行方法的调用。
- 服务消费方以本地调用方式 调用服务,它会将需要调用的方法,参数类型,参数传递给服务消费方的本地存根。
- 服务消费方的本地存根收到调用后,负责将方法,参数等数据组装成能够进行网络传输的消息体,并将该消息提传输给RPC通信者。
- 消费端的RPC通信者通过sockets将消息发送到服务端,由服务端的RPC通信者接收。
- 服务端将收到的信息传输给服务提供方的本地存根。
- 服务提供方的本地存根根据反序列化的结果解析出服务调用的方法,参数类型,参数等信息,并调用服务提供方服务。
- 服务提供方执行对应的方法后,将执行结果返回给服务提供方的本地存根
- 服务提供方的本地存根将返回结果序列化,并打包成可传输的消息体,传递给服务端的RPC通信者
- 服务端的PRC通信者通过sockets将消息发送给消费端,由消费端的PRC通信者接收,消费端将收到的消息传递给服务消费方的本地存根。
- 服务消费方的本地存根收到消息后将消息对象反序列化,反序列化出来的是方法的执行结果,并将结果传递给服务消费者。
- 服务消费者得到最终的执行结果。
RPC框架
Dubbo
dubbo提供了多种通信方案与编解码方案的组合。
SPI设计 Service Provider Interface
服务端 :
Maven 中配置 spring 和dubbo
1 2 3 4 5 6 7 8 9 10 11 12
| <dependencies> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>double</artifactied> <version>2.7.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.16.RELEASE</version> </dependency> </dependencies>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public interface DemoService{ String sayHello(String name ); }
import dubbo.samples.api.DemoServices; import org.apache.dubbo.rpc.RpcContext; import java.text.SimpleDateFormat; import java.util.Date ; public class DemoServiceImpl implements DemoService{ @Override public String sayHello(String name){ System.out.println("["+ new SimpleDateFormat("HH:mm:ss").format(new Date()) +"] Hello" + name + ", request from consumer: " + RpcContext.getContent().getRemoteAddress()); } }
|
服务暴露
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?xml version=“1.0” encoding="UTF-8"?> <beans xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>" xmlns:dubbo="<http://dubbo.apache.org/schema/dubbo>" xmlns="<http://www.springframework.rog/schema/beans>" xmlns:context="<http://www.springframework.org/schema/context>" xsi:schemaLocation="<http://www.springframework.org/schema/beans> <http://www.springframework.org/schema/beans/spring-beans.xsd> <http://dubbo.apache.org/schema/dubbo> <http://dubbo.apache.org/shcema/dubbo.xsd> <http://www.springframework.org/schema/context> <http://www.springframework.org/schema/context/spring-context.xsd> <context:property-placeholder/> <dubbo:application name="provider"/> <dubbo:registry address="multicast: <dubbo:protocol name="dubbo" port="20880" /> <bean id="demoService" class="dubbo.samples.impl.DemoServiceImpl" /> <dubbo:service interface="dubbo.samples.api.DemoService" ref= "demoService" group="test" /> </beans>
|
创建启动类
1 2 3 4 5 6 7 8 9 10 11 12 13
| import org.springframework.context.support.ClassPathXmlApplicationContext ;
import java.util.concurrent.CountDownLatch;
public class DemoProvider { public static void main(String[] args ) throws Exception{ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml"); context.start(); System.out.println("dubbo service started"); new CountDownLatch(1).await(); }
}
|
消费端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi = "<http://www.w3.org/2001/XMLSchema-instance>" xmlns:dubbo="<http://dubbo.apache.org/schema/dubbo>" xmlns="<http://www.springframework.org/schema/beans>" xmlns:context= "<http://www.springframework.org/schema/context>" xsi:schemaLocation="<http://www.springframework.org/schema/beans> <http://www.springframework.org/schema/beans/spring-beans.xsd> <http://dubbo.apache.org/schema/dubbo> <http://dubbo.apache.org/schema/dubbo.xsd> <http://www.springframework.org/schema/context> <http://www.springframework.org/schema/context/spring-context.xsd>"> <context:property-placeholder /> <dubbo:application name="consumer" /> <dubbo.reference id="demoService" check="false" interface="dubbo.samples.api.DemoService" url="127.0.0.1:20880" group="text"/> </beans>
|
调用发起
1 2 3 4 5 6 7 8 9 10 11 12 13
| import dubbo.samples.api.DemoService; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoConsumer{ public static void main(String[] args){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml"); context.start(); DemoService demoService = (DemoService) context.getBean("demoService"); String hello = demoService.sayHello("world"); System.out.println(hello); } } }
|
gRPC
gRPC是由谷歌开发的一款RPC框架,其有如下特点
- 使用Protocol Buffers 作为序列化方案。序列化性能非常高
- 协议传输采用的是HTTP/2 使用字节码形式的流式传输在服务端与消费端通信
- 验证机制丰富
- gRPC中的错误码遵循GoogleAPI中定义的错误码规范
构建流程
编写IDL文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| syntax = "proto3"; option java_multiple_files = true ; option java_package = "greeter"; option java_outer_classname = "MessageProto"; package greeter;
message Request { string name = 1; }
message Response{ string message = 1; }
greeter.proto syntax = "proto3";
option java_multiple_files = true ; option java_package = "greeter"; option java_outer_classname = "GreeterProto"; package greeter ;
import "greeter/message.proto";
service Greeter{ rpc SayHello( Request ) returns (Response) {} }
|
配置 proto依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <dependency> <groupId>io.grpc</groudId> <artifactId>grpc-all</artifactId> <version>1.32.1</version> </dependency>
<build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.12.0:exe: ${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.32.1:exe: ${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
<properties> <javaOutputDirectory>${project.basedir}/src/main/java-message</javaOutputDirectary> <protocPluginOutputDirectory>${project.basedir}/src/main/java-grpc</protocPluginOutputDirectory> </properties>
|
编译proto文件
实现具体服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package grpc ; import greeter.GreeterGrpc ; import greeter.Request ; import greeter.Response; import io.grpc.stub.StreamObserver;
public class GreeterRpcService extends GreeterGrpc.GreeterImplBase{ @Override public void sayHello(Request request, StreamObserver<Response>responseObserver){ String name = request.getName(); Response resp = Response.newBuilder(); .setMessage("Hello" + name +"!") .build(); responseObserver.onNext(resp); responseObserver.onCompleted(); } }
package grpc ; import io.grpc.ServerBuilder; import java.io.IOException;
public class GrpcProvider{ private final Server server ; public GrpcProvider(int port){ server = ServerBuilder.forPort(port) .addService(new GreeterRpcService()) .build(); } public void start() throws IOException{ server.start(); } public void shutdown(){ server.shutdown(0); } }
package grpc ; import greeter.GreeterGrpc; import greeter.Request; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder;
public class GrpcConsumer{ private final GreeterGrpc.GreeterBlockingStub blockingStub ;
public GrpcConsumer(String host,int port ){ ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host,port) .usePlaintext() .bulid(); blockingStub = GreeterGrpc.newBlockingStub(managedChannel); } public String sayHello(String name){ greeter.Request greeting = Request.newBuilder() .setName(name) .build(); greeter.Response resp = blockingStub.sayHello(greeting); return resp.getMessage(); } }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12
| package grpc ;
public static void main(String[] args) throws Exception{ int port = 8000; GrpcProvider server = new GrpcProvider(port); server.start(); GrpcConsumer client = new GrpcConsumer("127.0.0.1",port); String reply = client.sayHello("World"); System.out.println(reply); server.shutudown(); } }
|
总结
RPC(Remote Procedure Call)协议是一种通过网络在本地调用远程服务的方法,表现为本地程序发起函数调用请求,由远端服务器执行对应逻辑并返回结果。在整个调用链中,RPC 通常涉及五个关键角色:
- 服务调用方(Consumer)
- 调用方的本地存根(Client Stub)
- 通信模块(RPC Runtime)
- 服务端的本地存根(Server Stub)
- 服务提供者(Provider)
其典型流程包括:
- 服务暴露 :服务端将接口实现绑定到端口,或注册到注册中心;
- 服务发现 :客户端获取服务地址,可通过直连或注册中心方式;
- 服务引用 :客户端通过服务名定位目标服务并建立连接;
- 方法调用 :客户端发起远程调用,请求经网络传输、反序列化,在服务端执行后返回结果。
本篇展示了两种主流的 RPC 框架实现:
- Dubbo
Dubbo 采用“连接与业务解耦”的设计,通信细节通过 XML 配置完成,开发者只需专注业务逻辑。服务通过注册中心统一管理,客户端基于服务名发起远程调用,实现了高度抽象与服务治理的灵活性。
- gRPC
gRPC 基于 Protocol Buffers 定义接口与消息格式,使用 HTTP/2 作为传输协议,具备高效序列化、低延迟通信的优势。与 Dubbo 相比,gRPC 更加轻量,无需注册中心,服务端启动即提供服务,客户端直连发起调用,流程清晰、部署简洁。
总体而言,Dubbo 更适用于需要注册中心、服务治理、分布式架构管理的企业级应用,而 gRPC 更适合追求高性能、轻量级、云原生部署的微服务场景。
为什么选择 RPC 而不是 REST?
相较于 REST 接口,RPC 更适用于 高性能、强耦合、明确调用语义的场景 。以下是主要对比与选择理由:
1. 调用语义:函数调用 vs 资源操作
- REST 是基于 HTTP 标准语义的资源描述模型,强调“动词+资源”,使用如
GET
、POST
、DELETE
等标准方法对 URI 所代表的资源进行操作, 关注的是状态的变更和资源的表述 ;
- RPC 更像是传统意义上的“函数调用”,客户端直接调用远程函数, 明确指向某个具体服务的某个方法 ,调用粒度更细、控制更强。
2. 传输协议与性能
- REST 通常依赖 HTTP/1.1 协议 ,每次请求带有较多的冗余头部信息,如 User-Agent、Cookie、Accept 等,传输体积大,处理开销高。
- RPC 可以基于更加高效的协议(如 gRPC 使用的 HTTP/2 + Protobuf),在序列化、压缩、双向流式通信、连接复用等方面远优于 REST。
3. 协议灵活性与扩展能力
- REST 固定使用 HTTP 语义,语法简单但缺乏灵活性,复杂行为(如事务、回调、流式数据)实现困难。
- RPC 支持自定义协议结构,字段更细粒度控制,支持流式调用、元信息传输、链路追踪等增强能力,且易于加入认证、压缩、加密等机制。
4. 安全性与内聚性
- REST 更偏向无状态通信,服务器端对请求行为缺乏强验证能力,且由于请求行为是“声明式”的,缺少对调用链的明确控制。
- RPC 可以集成 TLS、认证签名、身份控制,结合注册中心和服务网格形成闭环的安全控制链路, 安全性和服务边界更强 。