在分布式计算中,远程过程调用(RPC)是指计算机程序导致过程(子程序)在不同的地址空间(通常为一个开放网络中的另一台计算机)执行,其编写方式就像是普通(本地)过程调用一样,程序员无需明确编写远程交互的细节。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受响应进行信息交互的系统。
RPC是一种进程间通信的模式,程序分布在不同的地址空间中。如果在同一主机里,RPC可以通过不同的虚拟地址空间(即使使用相同的物理地址)进行通讯,而在不同主机间,则通过不同的物理地址进行交互。许多技术(通常是不兼容)都是基于这种概念而实现的。
RPC调用本质上是一种协议,允许一台计算机上的程序在另一天计算机上执行代码,而无需程序员明确编写此交互的代码。RPC类似于调用不同系统中可用的函数或方法,因此叫做远程过程调用。
RPC的一个显著特点是它能够掩盖网络接口的复杂性,使得开发人员可以专注于应用程序的功能,而无需深入研究网络协议的复杂性。

RPC实践,主要是以gRPC为例进行实践,因为我用的是go语言,所以选择的是grpc-go来进行实践。
gRPC的原理图如下,具体可参考:Introduction to gRPC
具体可以参考:gRPC for Go Quick Start
可以直接按照官方文档提供的步骤来,先完成前置条件,安装好protoc-gen-go和progoc-gen-go-grpc即可。
然后编写proto文件
syntax = "proto3"; option go_package="go-study/blogs/grpc_study/helloworld/hello"; option java_multiple_files = true; option java_package = "io.grpc.examples.helloworld"; option java_outer_classname = "HelloWorldProto"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; } 使用protoc直接生成对应的pb文件即可,具体的命令:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto 生成的文件如下:
最后我们主要使用hello_grpc.pb.go文件里面对应的Client和Server的方法即可。
关于client和server的代码,可以参考:grpc-go examples helloworld
分别运行client和server,当client运行时会发送消息给server,收到消息后,server会处理消息并返回响应。
$ go run .\server.go 2024/07/14 15:42:30 server listening at [::]:50051 2024/07/14 15:42:42 Received: testing $ go run .\client.go --name=testing 2024/07/14 15:42:42 Greeting: Hello testing RPC与REST是API设计中的两种不同的架构风格。API则是允许两个软件组件使用一组定义和协议相互通信的机制。软件开发人员使用之前开发的组件或者第三方的组件来执行功能,因此不必从头开始编写所有内容。RPC API允许开发人员在外部服务器中调用远程函数,就好像它们在本地环境一样。
在RPC中,客户端在服务器上进行远程函数(也称为方法或过程)调用,通常在调用期间会向服务器传递一个或者多个数据值。
而REST客户端则是请求服务器针对服务器特定资源执行操作,操作仅限于创建、读取、更新和删除,并以HTTP动词或者HTTP方法的形式传达。
RPC侧重函数或操作,而REST侧重于资源或对象。
RPC原则:
REST原则
当下REST API已经成为了主流,因为它更易于开发人员理解和实施,但RPC并没有消失,依然会在适合的应用场景中使用(如gRPC,允许客户端与服务器之间流式通信,而非请求和相应数据交换模式)。
为了高可用,在生产环境中服务提供方都是以集群的方式对外提供服务,集群里面的这些IP随时会发生变化,此时我们需要能够实时获取对应服务节点,而这个获取的过程我们称作“服务发现”。
类似于Dubbo,采用的是以Zookeeper作为服务注册中心,注册中心在RPC场景下负责保存服务端应用的信息,服务端注册接口信息和自身地址到注册中心,客户端从注册中心读取和订阅需要调用的地址列表,框架如图所示:

在etcd中,etcd提供了一个gRPC解析器来支持备用名称系统,该系统从etcd获取端点以发现gRPC服务,底层基于监听以服务名称为前缀的秘钥更新机制实现。
具体实现则是通过etcd提供的etcdnaming的能力实现获取对应的grpc resolver,从而获取到对应的存储在etcd的服务器信息,从而获取到服务器的请求地址。
import ( "go.etcd.io/etcd/client/v3" etcdnaming "go.etcd.io/etcd/client/v3/naming/resolver" "google.golang.org/grpc" ) func main() { cli, err := clientv3.NewFromURL("http://localhost:2379") if err != nil { // ... } builder, err := etcdnaming.NewBuilder(cli) if err != nil { // ... } conn, gerr := grpc.Dial("my-service", grpc.WithResolvers(builder), grpc.WithBlock(), ...) } 而对应的管理服务端点以及添加/删除服务端点则是通过操作etcd来实现。
添加端点
ETCDCTL_API=3 etcdctl put my-service/1.2.3.4 '{"Addr":"1.2.3.4","Metadata":"..."}' 删除端点
ETCDCTL_API=3 etcdctl del my-service/1.2.3.4 使用租约注册端点
使用租约注册端点可以确保如果主机无法保持活动心跳(例如,机器发生故障),它将从服务中删除。
lease=`ETCDCTL_API=3 etcdctl lease grant 5 | cut -f2 -d' '` ETCDCTL_API=3 etcdctl put --lease=$lease my-service/1.2.3.4 '{"Addr":"1.2.3.4","Metadata":"..."}' ETCDCTL_API=3 etcdctl lease keep-alive $lease 通过etcd加持gRPC可以实现RPC服务的注册发现,从而使得服务在保证高可用的情况下,RPC服务请求调用也能顺利完成。
很早之前就接触过RPC,也接触过服务的注册与发现,最早是学习Java的时候接触的Dubbo,工作后转到了Go,最近是看到了有etcd+gRPC的使用,带着看一看的想法就把RPC顺带着给一起复习下了。
在当下的分布式系统的情况下,RPC服务基本都需要考虑高可用,所以注册中心就成为了必须要解决的一个问题。