对RPC的理解
什么是RPC
RPC
是Remote Procedure Call
的缩写,中文译为远程过程调用,通俗一点来说就是通过网络调用部署在远程服务器上的函数或方法,如下图所示:
RPC与HTTP
谈到RPC
,往往会让人联想到另一种调用远程服务的方式:HTTP
。
那为什么有了HTTP
,还需要RPC
呢?
其实使用RPC
在不同主机进程间通讯的时间要早于HTTP
出现的时间。
因此,应该这么问:既然有了RPC
为何还需要HTTP
呢?
我们知道,任何网络应用程序之间通讯都是基于TCP
协议(当然可以是UDP
)。
TCP
是传输层协议,其作用之一便是将从网络层接收的数据传给应用层。
HTTP
是一个应用层协议,HTTP
协议规定一条HTTP
报文由请求行、请求头和消息体组成,因此每条HTTP
报文都有固定的格式。
使用RPC
的方式进行通讯时,传输层依然是TCP
协议,但是应用层则需要通讯双方约定好数据格式,相当于自定义一个应用层协议,因此RPC
有各种不同的实现,并没有统一的规范。
gRPC框架的使用
gRPC
是一个高性能开源的RPC
框架,支持Go
,C++
,Java
,PHP
,Ruby
,Python
等不同编程语言。
图片来自于grpc官网
Protocol Buffers
Protocol Buffers
是Google
开发的一种与语言、平台无关的数据序列化机制,这种机制由几个部分组成:
protoc
编译器,用于编译.proto
文件。- 以
.proto
为后缀的IDL
声明文件,用于定义一个RPC
服务。 - 底层支持通讯并进行编码与解码的库。
Protocal Buffers
有以下几个特征:
Protocal Buffers
和JSON
类似,用于序列化数据,不过与比于JSON
其体积更小,因此传输也更快。- 支持多种编程语言。
- 可以非常快速传输与解析。
.proto文件
.proto
文件用于声明使用gRPC
进行通讯服务名称、请求数据类型、顺序与响应数据类型、顺序等信息。
.proto
文件的第一行必须是:
syntax = "proto3";
如果没有声明为proto3
,编译器会以proto2
的语法解析.proto
文件。
message
关键字用于定义一个消息类型:
message SearchRequest { string query = 1; int32 page_number = 2; int32 results_per_page = 3; }
同一个.proto
文件里可以定义多个消息类型:
message SearchRequest { string query = 1; int32 page_number = 2; int32 results_per_page = 3; } message SearchResponse { //... }
service
关键字用于声明一个服务,格式如下:
service Search { rpc Search (SearchRequest) returns (SearchReply) {} }
编写好的.proto
文件,要使用protoc
编译进行编译,生成目标语言的代码。
开发工具安装
当然在开发之前,除了安装Go语言环境外,还需要安装以下几个工具:
- protoc
- protoc-gen-go
- protoc-gen-go-grpc
protoc
protoc
是.proto
文件的编译器,其作用是将.proto
文件中声明的信息转为目标语言的代码。
protoc
可以从以下地址下载:https://github.com/protocolbuffers/protobuf/releases
下载后,将其配置到PATH
路径下即可。
protoc-gen-go与protoc-gen-go-grpc
如果要protoc
编译器可以生成go
代码,还需要安装protoc-gen-go
和protoc-gen-go-grpc
插件。
protoc-gen-go
与protoc-gen-go-grpc
插件用于生成go
以及grpc
代码,这两个插件由protoc
命令调用。
使用go install
将两个命令安装到GOPATH/bin
目录下:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
GOPATH/bin目录要配置到PATH目录下
案例实战
安装了相关工具以及了解了Protocol Buffers
与.proto
文件,下面我们通过一个实际案例来了解RPC
应用的开发。
创建项目
首先执行以下命令创建一个Go项目:
$ mkdir test $ cd test $ go mod init test
定义.proto文件
用gRPC
开发RPC
应用的第一件事就是定义.proto
文件,在这个项目中,我们在user
目录下创建user.proto
文件:
$ mkdir user $ touch user.proto
在user.proto
文件中输入以下代码:
syntax = "proto3"; option go_package = "test/user"; //定义两个服务 service User { rpc GetUser (UserId) returns (UserInfoReply) {} rpc AddUser (AddUserRequest) returns(UserId){} } message UserId { int32 id = 1; } message UserInfoReply { int32 id = 1; string name = 2; string email = 3; } message AddUserRequest { string name = 1; string email = 2; }
编译.proto文件:
.proto
文件编写完成后,执行protoc
命令编译该文件,生成目标语言代码:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ user/user.proto
编译成功后会在user
目录生成user.pb.go
和user_grpc.pb.go
两个文件。
编写服务端代码
下面是gRPC
应用的服务端代码:
package main import ( "context" "log" "net" "test/user" "google.golang.org/grpc" ) type userServer struct { user.UnimplementedUserServer } //[1] func (s *userServer) GetUser(ctx context.Context, in *user.UserId) (*user.UserInfoReply, error) { log.Printf("请求用户id为: %d", in.GetId()) return &user.UserInfoReply{Id: 1, Name: "程序员读书", Email: "test@163.com"}, nil } //[2] func (s *userServer) AddUser(ctx context.Context, in *user.AddUserRequest) (*user.UserId, error) { log.Printf("你要添加的用户名称为: %s,邮箱为:%s", in.GetName(), in.GetEmail()) return &user.UserId{Id: 2}, nil } func main() { listen, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() user.RegisterUserServer(s, &userServer{}) log.Printf("server listening at %v", listen.Addr()) if err := s.Serve(listen); err != nil { log.Fatalf("failed to serve: %v", err) } }
在上面的代码中,主要完成以下几件事:
- 创建一个监听器监听
50051
端口 - 通过
grpc.NewServer()
创建RPC
服务器,将服务对象userServer
绑定服务器 - 将监听器传给
RPC
服务器以启动服务。
编写客户端代码
package main import ( "context" "log" "os" "time" "test/user" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() u := user.NewUserClient(conn) userInfo, err := u.GetUser(ctx, &user.UserId{Id: 1}) if err != nil { log.Fatalf("user nto found: %v", err) } userId,err := u.AddUser(ctx,&user.AddUserRequest{Name:"test",Email:"test@test.com"}) }
小结
本文主要介绍了使用gRPC
与Go语言进行RPC
应用的开发,总结起来就是以下几点:
- 通过网络调用远程主机的函数,称为
RPC
。 gRPC
是一个实现RPC的框架,支持多种编程语言。gRpc
使用.proto
文件描述一个RPC
服务,并用protoc
命令生成目标语言的代码。
到此这篇关于重学Go语言之如何开发RPC应用的文章就介绍到这了,更多相关Go RPC内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!