1、概述
1.1 什么是GRPC
RPC的全称是Remote Procedure Call,远程过程调用。RPC是一种协议,它实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。
而gRPC又是什么呢?用官方的话来说:
A high-performance, open-source universal RPC framework
gRPC是一个高性能的、开源的通用的RPC框架。
在gRPC中,我们称调用方为client,被调用方为server。 跟其他的RPC框架一样,gRPC也是基于”服务定义“的思想。简单的来讲,就是我们通过某种方式来描述一个服务,这种描述方式是语言无关的。在这个”服务定义“的过程中,我们描述了我们提供的服务的服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。
也就是说,在定义好了这些服务、这些方法之后,gRPC会屏蔽底层的细节,client只需要直接调用定义好的方法,就能拿到预期的返回结果。对于server端来说,还需要实现我们定义的方法。同样的,gRPC也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。
你可以发现,在上面的描述过程中,所谓的”服务定义“,就跟定义接口的语义是很接近的。我更愿意理解为这是一种”约定“,双方约定好接口,然后server实现这个接口,client调用这个接口的代理对象。至于其他的细节,交给gRPC。
此外,gRPC还是语言无关的。你可以用C++作为服务端,使用Golang、Java等作为客户端。为了实现这一点,我们在”定义服务“和在编码和解码的过程中,应该是做到语言无关的。
如下图所示就是一个典型的RPC结构图。
通过上图可以看到gRPC使用了Protocol Buffers。本文不会展开来讲Protocol Buffers(详细proto语法参见:Golang使用Protobuf),你可以把他当成一个代码生成工具以及序列化工具。这个工具可以把我们定义的方法,转换成特定语言的代码。比如你定义了一种类型的参数,他会帮你转换成Golang中的struct结构体,你定义的方法,他会帮你转换成func函数。此外,在发送请求和接受响应的时候,这个工具还会完成对应的编码和解码工作,将你即将发送的数据编码成gRPC能够传输的形式,又或者将即将接收到的数据解码为编程语言能够理解的数据格式。
1.2 使用场景
- 低延时、高可用的分布式系统;
- 移动端与云服务端的通讯;
- 使用protobuf,独立于语言的协议,支持多语言之间的通讯;
- 可以分层扩展,如:身份验证,负载均衡,日志记录,监控等;
1.3 gRPC 与 RESTful API比较
特性 | gRPC | RESTful API |
---|---|---|
规范 | 必须.proto | 可选 OpenAPI |
协议 | HTTP/2 任意版本的 | HTTP 协议 |
有效载荷 | Protobuf(小、二进制) | JSON(大、易读) |
浏览器支持 | 否(需要 grpc-web) | 是 |
流传输 | 客户端、服务端、双向 | 客户端、服务端 |
代码生成 | 是 | OpenAPI + 第三方工具 |
2、 环境配置
2.1 安装Protocol Buffers
安装用于生成gRPC服务代码的协议编译器
下载地址:https://github.com/google/protobuf/releases
- bin 目录下的 protoc 是可执行文件。
- include 目录下的是 google 定义的.proto文件,我们import "google/protobuf/timestamp.proto"就是从此处导入。
我们需要将下载得到的可执行文件protoc所在的 bin 目录加到我们电脑的环境变量中。
或者直接将解压后得到的protoc.exe二进制文件移动到$GOPATH/bin里。
2.2 安装Protoc Plugin
编译器插件
我们是使用Go语言做开发,接下来执行下面的命令安装protoc的Go插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
protoc-gen-go-grpc是Google协议缓冲区编译器生成Go代码的插件。
该插件会生成一个后缀为_grpc.pb.go的文件,其中包含:
一种接口类型(或存根) ,供客户端调用的服务方法。
服务器要实现的接口类型。
上述命令会默认将插件安装到
G
O
P
A
T
H
/
b
i
n
,为了
p
r
o
t
o
c
编译器能找到这些插件,请确保你的
GOPATH/bin,为了protoc编译器能找到这些插件,请确保你的
GOPATH/bin,为了protoc编译器能找到这些插件,请确保你的GOPATH/bin在环境变量中。
两次安装后在GOPATH/bin目录下生成exe,如下图:
安装检查
2.2 获取gRPC
go get google.golang.org/grpc
这一步安装的是gRPC的核心库。
2.3 获取protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
安装protoc-gen-go-grpc用于.proto–>***_grpc.pb.go。
3、 gRPC入门示例
代码编写:
- 首先要编写message.proto文件
rgpc/pkg/proto/message.proto
syntax = "proto3";
// 这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中,../pb代表在当前目录的上一级pb目录中生成,message代表了生成的go文件的包名是message。
option go_package = "../pb;pb";
message MessageResponse {
string responseSomething = 1;
}
message MessageRequest {
string saySomething = 1;
}
service MessageSender {
rpc Send(MessageRequest) returns (MessageResponse) {}
}
- 执行命令生成代码,代码将生成再pb包下
protoc --go_out=. message.proto
protoc --go-grpc_out=. message.proto
- Server端代码
rgpc/pkg/service/main.proto
package main
import (
"google.golang.org/grpc"
"log"
"net"
"rgpc-demo/pkg/pb"
"rgpc-demo/pkg/serviceImpl"
)
func main() {
srv := grpc.NewServer()
pb.RegisterMessageSenderServer(srv, serviceImpl.MessageSenderServerImpl{})
listener, err := net.Listen("tcp", ":8002")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
err = srv.Serve(listener)
if err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
rgpc/pkg/serviceImpl/MessageSenderServerImpl.go
package serviceImpl
import (
"context"
"log"
"rgpc-demo/pkg/pb"
)
type MessageSenderServerImpl struct {
*pb.UnimplementedMessageSenderServer
}
func (MessageSenderServerImpl) Send(context context.Context, request *pb.MessageRequest) (*pb.MessageResponse, error) {
log.Println("receive message:", request.GetSaySomething())
resp := &pb.MessageResponse{}
resp.ResponseSomething = "roger that!哈哈哈"
return resp, nil
}
- Client端
rgpc/pkg/client/main.go
package main
import (
"context"
"google.golang.org/grpc"
"log"
"rgpc-demo/pkg/pb"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8002", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewMessageSenderClient(conn)
resp, err := client.Send(context.Background(), &pb.MessageRequest{SaySomething: "hello world!第二次"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Println("receive message:", resp.GetResponseSomething())
}
- 结果