RPC 保姆级教程

3,769 阅读6分钟

RPC 是什么?

RPC(Remote Procedure Call),即远程过程调用,主要是为了解决在分布式系统中,服务之间的调用问题。

首先我们需要简单了解一下什么是分布式系统以及分布式系统是用来解决什么问题的?

分布式系统顾名思义就是将一个完成任务 A 的整体的系统拆分成多个模块服务分别部署在不同的计算机节点上,这些模块通过网络通讯协同工作完成任务 A。分布式系统主要就是用多台性能普通的计算机完成单个高性能计算机难以完成的任务,毕竟单个计算机的性能再强大也是有上限的,多台普通计算机理论上是可以无限拓展的。

了解了分布式,现在就需要了解为什么解决分布式系统中通讯问题需要采用 RPC?

首先在分布式系统中,部署在不同机器上的服务需要协同工作就必须要相互通讯,它们之间的通讯并不能像是本地函数调用一样轻松,本地函数的调用因为是在同一地址空间,所以通过方法栈和参数栈就可以实现相互之间通讯,但部署在不同机器上的服务,并没有共用的地址空间,所以只能通过网络通讯。

为了下面举例更具象化,这里不同机器的服务用 A 服务和 B 服务做代指,场景是 A 服务需要调用 B 服务的方法。 说到网络通讯,很多人第一想法就是,让被调用的 B 服务提供一个接口,A 服务请求接口实现调用,这种方法确实能够解决跨服务调用的问题,但从成本的角度考虑,每一次方法调用都需要一次 http 请求的消耗,每次都得写一串 httpClient 请求等信息,费时费力。

为了优化这个重复编写 httpClient 的操作,可以用代理模式实现调用,在这个代理内部实现 httpClient 请求等一系列繁杂的操作。目前其实有很多 RPC 框架是采用这种设计理念,如 Motan,dubbo 等。

由此可见 RPC 不仅仅是为了解决服务之间的调用,更是为了让服务之间的调用像本地函数调用一样,方便快捷。

RPC 的基本流程?

RPC 流程大致分为三个部分网络传输,函数名映射,序列化和反序列化。

  1. A 服务想要调用B 服务的方法,首先得通过底层 RPC 框架的连接上B 服务,将需要的信息发送给 B 服务,这就是 RPC 中的网络通讯。第一个想到的就是 HTTP 协议(HTTP1.1)来进行数据传输,但针对一些追求效率的场景,传输数据显然不必用到文本传输的应用协议,使用二进制传输明显更加快速,如:TCP,HTTP2 等。
  2. 如果将 RPC 调用类比本地函数调用,首先必须的知道函数名称才能进行调用。在 RPC 中,所有函数都必须有一个唯一的标识 ID,这个 ID 在所有进程都是唯一确定的。服务 A 在发出调用请求时,一定要将这个 ID 传输给 B 服务,B 服务将根据这个 ID 找到对应的函数进行调用返回结果给 A 服务。
  3. 因为采用了二进制协议进行通讯,所以必然和涉及到一个序列化和反序列化的过程,其实这个过程可以理解为将参数对象转换成二进制字节码的过程。

具体流程参考下图:

1.jpg

gRPC 是什么?

gRPC 是一款由 Google 开发的 RPC 框架,支持 C,Python,Go,Nodejs 等多种语言。采用 Protobuf 进行数据序列化,提高数据的压缩率,如果有不了解 Protobuf 的,可以参考:juejin.cn/post/688552…。网络传输协议使用的是 HTTP2.0,关于 HTTP2.0 可以参考:juejin.cn/post/694313…

使用 gRPC 主要分为三步:

  1. 编写 .proto pb 文件,制定通讯协议。
  2. 利用对应插件将 .proto pb文件编译成对应语言的代码。
  3. 根据生成的代码编写业务代码。 这里使用 Go 语言作为示例:

首先我们需要安装对应平台的 protobuf 压缩包,下载地址:github.com/protocolbuf…, 这里以 Mac (Catalina)平台为例下载 protoc-3.16.0-rc-1-osx-x86_64.zip,将包解压后得到如下目录:

image.png

将 bin 目录中的二进制文件添加到环境变量中,一般会将 protoc 二进制文件复制到 GOPATH 目录下的 bin 目录下统一管理

vim ~/.zshrc
alias protoc="/Users/{username}/{path}/bin/protoc"
source ./.zshrc

修改环境变量后,可以通过 protoc --version 来判断 protoc 是否安装成功。

准备好 protoc 的环境后还需要安装 Go 专用的 protoc 生成器

go get -u github.com/golang/protobuf/protoc-gen-go

安装完成后,在 GOPATH 目录下会生成可执行文件,通过 protoc 命令调用插件对 pb 文件进行编译即可。

接下来还需要安装 gRPC 框架

go get -u google.golang.org/grpc
或
go get -u github.com/grpc/grpc-go

基本环境和插件都已经准备完毕,接下来就是编写代码。先编写 user.proto 文件:

syntax = "proto3";
package userRpc;
option go_package="api/protobuf-spec/user";

message UserReq {
    int32 user_id = 1;
}

message UserResp {
    string user_name = 1;
}

service MainService {
    rpc GetUserName(UserReq) returns (UserResp);
}

编写完 user.proto 文件后,在根目录下新建编译指令中输出目录 service,在根目录下使用 protoc 插件对文件进行编译:protoc -I api/ api/protobuf-spec/user/user.proto --go_out=plugins=grpc:.得到 user.pb.go 文件

这里需要注意两点:

  1. 如果输出到指定文件夹,一定先要创建目标输出文件夹,否则会报错缺少输出文件夹。
  2. 在编写 .proto 文件时一定需要写上 go_package 配置,否则将编译报错。

获取编译后的 .pb.go 文件后,就可以编写客户端和服务端代码了。

服务端代码

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"grpc/api/protobuf-spec/user"
	"net"
	"strconv"
)

type server struct {}

func (s *server) GetUserName(ctx context.Context, request *user.UserReq) (*user.UserResp, error){
	return &user.UserResp{UserName: "name" + strconv.FormatInt(int64(request.UserId), 10)}, nil
}

func main() {
	fmt.Print("start server...")
	// listen port
	lis, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Printf("listen port error: %s", err)
		return
	}
	
	// new api server
	grpcServer := grpc.NewServer()
	user.RegisterMainServiceServer(grpcServer, &server{})
	if err := grpcServer.Serve(lis); err != nil {
		fmt.Printf("Server start error: %s", err)
		return
	}
}

客户端代码

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"grpc/api/protobuf-spec/user"
)

func main() {
	conn, err := grpc.Dial(":8080", grpc.WithInsecure())
	if err != nil {
		fmt.Printf("connect failure %s", err)
		return
	}
	defer conn.Close()

	// create client
	cli := user.NewMainServiceClient(conn)

	ret, err := cli.GetUserName(context.Background(), &user.UserReq{UserId: 13})
	if err != nil {
		fmt.Printf("call srever func failure %s", err)
		return
	}
	fmt.Printf("call success %s /n", ret.UserName)
}

示例代码仓库链接:github.com/Monetchang/…

BloomRPC 是什么?

BloomRPC 是一款用来调试 gRPC 项目的 GUI 工具,如同 Postman 在 Restful API 开发中扮演的地位。

下载地址: github.com/uw-labs/blo…

BloomRPC 文档中提供了几种下载方式:

Mac平台可以直接通过 brew 来安装:

brew install --cask bloomrpc

也可以通过 clone 代码仓库的形式来安装:

git clone https://github.com/uw-labs/bloomrpc.git
cd bloomrpc

yarn install && ./node_modules/.bin/electron-rebuild
npm run package

使用方式十分简单,左侧边栏右上角加号导入 pb 文件,中间 Editor 输入框输入参数,右侧 Response 查看返回结果。详细的操作流程可以参考 github 地址 README:github.com/uw-labs/blo…