RPC
,全称remote procedure call
,即远程过程调用,也是一种客户端-服务器(client/server
)模型,通
俗讲RPC就是客户端像调用本地方法一样调用了远端服务中的方法,而无需关心具体调用细节。
在不考虑调用函数逻辑情况下,调用RPC方法体验与本地函数调用基本一致。
不过因为RPC涉及网络调用,服务两端并不存在一个内存空间内,这样就会带来一些新问题。
1、服务之间方法如何映射?
本地调用通过函数指针指定调用的函数,而RPC调用为了确保执行正确的函数,则需要客户端与服务端有一致函数
映射,确保服务端执行的是客户端调用的,简单实现可以用过双方维护一套映射表,一般通过动态代理实现。
2、服务之间传输的信息结构?
本地调用只需将参数压入栈中,函数从栈中读取即可。而RPC调用,因为不在相同的内存空间,无法通过内存传递
参数,并且需要通过网络传输,则需要一套序列化/反序列化机制,确保服务端将收到的 客户端的序列化后的参数
反序列化后与实际传参意义一致。
3、服务之间如何通信?
客户端需要通过网络传输,将函数映射以及序列化后的内容发送给服务端,而服务端则需要返回内容给客户端。中
间则需要选择传输协议,可以是传输层协议,也可以是应用层协议,如gRPC就使用HTTP/2。
解决了上述三个问题,也就实现了一个基础的RPC框架。
下图为一个完成的RPC调用流程:
笼统的讲,封装好的HTTP接口形式也可算作RPC的一种。
gRPC
是一个高性能、开源、通用的RPC
框架,由Google推出,基于HTTP2
协议标准设计开发,默认采用
Protocol Buffers
数据序列化协议,支持多种开发语言。
gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。
在gRPC客户端可以直接调用不同服务器上的远程程序,使用姿势看起来就像调用本地程序一样,很容易去构建分
布式应用和服务。和很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述
直接调用需要的服务。客户端和服务端可以分别使用gRPC支持的不同语言实现。
gRPC官网:https://grpc.io/
HTTP2:https://http2.github.io/
Protocol Buffers:https://developers.google.com/protocol-buffers/
grpc中文文档:http://doc.oschina.net/grpc
Go版gRPC文档地址:https://grpc.io/docs/languages/go/
官方地址:https://github.com/grpc/grpc-go
在线手册:https://pkg.go.dev/google.golang.org/grpc
gRPC是google与2015年开源的RPC框架,相比于其他RPC框架,最显著的三大特点:
1、强大的IDL
gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、
hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。
2、多语言支持
gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本
grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、
Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。
3、HTTP2
gRPC基于HTTP2标准设计,所以相对于其他RPC框架,gRPC带来了更多强大功能,如双向流、头部压缩、多
复用请求等。这些功能给移动设备带来重大益处,如节省带宽、降低TCP链接次数、节省CPU使用和延长电池
寿命等。同时,gRPC还能够提高了云端服务和Web应用的性能。gRPC既能够在客户端应用,也能够在服务器
端应用,从而以透明的方式实现客户端和服务器端的通信和简化通信系统的构建。
1、高性能
基于HTTP/2的多路复用,减少TCP连接,首部压缩等特性以及ProtoBuf高效的二进制序列化,更高效的传输消
息,节省带宽,从而提高吞吐量。
2、流处理
HTTP/2为实时通信流提供了基础。除了简单的RPC,gRPC中支持三种流组合:
服务器流式响应
客户端流式发送
双向流
3、开发流程简单
相比于RESTful接口,无需思考请求方式,URL路径等规范,定义好proto请求响应结构,生成对应pb文件即可。
4、支持多语言
只需编写一份proto,通过命令行生成对应语言的Stub代码即可调用,而RESTful接口,每个调用方都需要实现一
套基础调用类。
下载地址:https://github.com/protocolbuffers/protobuf/releases
这里下载protoc-3.19.1-win64.zip:
https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-win64.zip
下载之后进行解压,解压完成后的目录:
将D:\DecompressionSoftwareInstall\protoc-3.19.1-win64\bin
添加到Path
环境变量中。
go get -u github.com/golang/protobuf/protoc-gen-go
# 安装protoc-gen-go包
go install github.com/golang/protobuf/protoc-gen-go
# 或者可以指定版本
# go install github.com/golang/protobuf/[email protected]
$ protoc --version
libprotoc 3.19.1
$ protoc-gen-go -v
protoc-gen-go: unknown argument "-v" (this program should be run by protoc, not directly)
地址:https://github.com/protocolbuffers/protobuf/releases
下载: protoc-3.19.1-linux-x86_64.zip
# 新建文件夹
$ mkdir protoc-3.19.1-linux-x86_64
# 解压
unzip protoc-3.19.1-linux-x86_64.zip -d protoc-3.19.1-linux-x86_64
# 切换目录
cd protoc-3.19.1-linux-x86_64/
# 查看文件
$ ll
total 4
drwxr-x---. 2 root root 20 Oct 29 2021 bin
drwxr-x---. 3 root root 20 Oct 29 2021 include
-rw-r-----. 1 root root 724 Oct 29 2021 readme.txt
$ ll ./bin
total 5284
-rwxr-x---. 1 root root 5407624 Oct 29 2021 protoc
protoc
为可执行文件。
# 查看版本
$ ./bin/protoc --version
libprotoc 3.19.1
将环境变量配置在/etc/profile
文件中:
export PATH=$PATH:/home/zhangshixing/protoc/protoc-3.19.1-linux-x86_64/bin/
添加完环境变量之后要记得执行source /etc/profile
。
go get -u github.com/golang/protobuf/protoc-gen-go
# 安装protoc-gen-go包
go install github.com/golang/protobuf/protoc-gen-go
# 或者可以指定版本
# go install github.com/golang/protobuf/[email protected]
# 查看版本
[root@zsx ~]# protoc-gen-go -v
protoc-gen-go: unknown argument "-v" (this program should be run by protoc, not directly)
编写一个 hello.proto
文件:
// /home/zhangshixing/protoc/temp/hello.proto
// 指定proto版本
syntax = "proto3";
// 指定包名
package mypackage;
// 指定文件生成的路径和包名
// ./hello为路径
// mytest为生成的包名
// 如果指定的go_package="./hello";则包名和路径同名(如果路径有多层,则包名和路径的最后一层相同)
option go_package="./hello;mytest";
// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloReply) {}
}
// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}
// HelloReply 响应结构
message HelloReply {
string message = 1;
}
编译自动生成go文件:
[root@zsx temp]# protoc -I . --go_out=plugins=grpc:. hello.proto
查看目录结构:
[root@zsx protoc]# tree temp
temp
├── hello
│ └── hello.pb.go
└── hello.proto
1 directory, 2 files
查看生成的hello.pb.go
文件:
[root@zsx temp]# cat ./hello/hello.pb.go
// /home/zhangshixing/protoc/temp/hello.proto
// 指定proto版本
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.19.1
// source: hello.proto
// 指定包名
package mytest
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
......
至此,gRPC的环境搭建完毕。
使用命令protoc -help
查看protoc支持的参数。
命令使用格式:protoc [OPTION] PROTO_FILES
。
可选参数:
-IPATH, --proto_path=PATH
:指定要在其中搜索导入的目录,可以多次指定,目录将按顺序搜索。如果未
给出,则使用当前工作目录。如果在这些目录中找不到,将检查--descriptor_set_in
描述符以查找所需的
protoc文件。
$ protoc -I ./protoc-file1 -I ./protoc-file2
--version
:显示版本信息并退出。
-h, --help
:显示此文本并退出。
--encode=MESSAGE_TYPE
:从标准输入中读取给定类型的文本格式消息,并将其以二进制形式写入标准输
出。消息类型必须在PROTO_FILES
或其导入中定义。
--deterministic_output
:使用--encode
时,确保映射字段是确定有序的。请注意,此顺序不是规范的,
并且会在构建或发布的协议中发生变化。
--decode=MESSAGE_TYPE
:从标准输入中读取给定类型的二进制消息,并将其以文本格式写入标准输出。消
息类型必须在PROTO_FILES
或其导入中定义。
--decode_raw
:从标准输入读取任意协议消息,并将文本格式的原始标签/值对写入标准输出。使用此标志
时不应给出任何PROTO_FILES
。
--descriptor_set_in=FILES
:指定文件的分隔列表,每个文件都包含一个
FileDescriptorSet(descriptor.proto中定义的协议缓冲区)。提供的每个PROTO_FILES
的FileDescriptor将从这
些FileDescriptorSet加载。如果FileDescriptor出现多次,将使用第一次出现。
-oFILE,--descriptor_set_out=FILE
:将包含所有输入文件的FileDescriptorSet(descriptor.proto中定
义的协议缓冲区)写入FILE。
--include_imports
:使用–descriptor_set_out时,还要在集合中包含输入文件的所有依赖项,以便集合是
自包含的。
--include_source_info
:使用--descriptor_set_out
时,不要从FileDescriptorProto中删除
SourceCodeInfo。这会导致更大的描述符,其中包括有关源文件中每个decl的原始位置以及周围注释的信
息。
--dependency_out=FILE
:以make期望的格式写入依赖项输出文件,这将输入文件路径的传递集写入
FILE。
--error_format=FORMAT
:设置打印错误的格式。格式可以是“gcc”(默认)或“msvs”(Microsoft Visual
Studio格式)。
--fatal_warnings
:使警告成为致命的(类似于gcc中的-Werr)。如果生成任何警告,此标志将使协议返
回非零退出代码。
--print_free_field_numbers
:打印给定原型文件中定义的消息的可用字段编号。组与父消息共享相同的
字段编号空间。扩展范围计为占用的字段编号。
--plugin=EXECUTABLE
:指定要使用的插件可执行文件。通常,协议在PATH中搜索插件,但您可以使用此标
志指定不在路径中的其他可执行文件。此外,EXECUTABLE的形式可能是NAME=PATH,在这种情况下,即使
可执行文件自己的名称不同,给定的插件名称也会映射到给定的可执行文件。
--cpp_out=OUT_DIR
:生成 C++ 标头和源。
--csharp_out=OUT_DIR
:生成C#源文件。
--java_out=OUT_DIR
: 生成Java源文件。
--js_out=OUT_DIR
:生成JavaScript源。
--kotlin_out=OUT_DIR
:生成Kotlin文件。
--objc_out=OUT_DIR
:生成 Objective-C标头和源。
--php_out=OUT_DIR
:生成PHP源文件。
--python_out=OUT_DIR
:生成Python源文件。
--ruby_out=OUT_DIR
:生成Ruby源文件。
@
:从文件中读取选项和文件名。如果指定了相对文件路径,则将在工作目录中搜索该文件。
--proto_path
选项不会影响该参数文件的搜索方式。文件的内容将在参数列表中的@
位置展
开。请注意,shell扩展不适用于文件的内容(即不能使用引号、通配符、转义、命令等)。每一行对应一个
参数,即使它包含空格。
使用protoc
命令编译.proto
文件,不同语言支持需要指定输出参数:
$ protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
完整示例:
$ protoc -I . --go_out=plugins=grpc:. hello.proto
-I
:指定import路径,可以指定多个-I参数,编译时按顺序查找,不指定时默认查找当前目录,这里的.
为当前目录。该目录中存放着某些proto文件相互依赖的proto文件。
--go_out
:golang编译支持。
plugins=plugin1+plugin2
: 指定插件,目前只支持grpc,即:plugins=grpc
。
末尾 :编译文件路径 .proto文件路径
:编译文件路径和.protoc文件路径都支持通配符,编译文件路径是指生
成的文件的存放路径。全路径的写法:
$ protoc -I /home/zhangshixing/protoc/temp --go_out=plugins=grpc:/home/zhangshixing/protoc/temp /home/zhangshixing/protoc/temp/hello.proto
grpc/grpc
https://github.com/grpc/grpc
grpc-go
https://github.com/grpc/grpc-go/
grpc-java
https://github.com/grpc/grpc-java
google/protobuf
https://github.com/google/protobuf
golang/protobuf
https://github.com/golang/protobuf
grpc-middleware
https://github.com/grpc-ecosystem/go-grpc-middleware
grpc-gateway
https://github.com/grpc-ecosystem/grpc-gateway