从这篇开始,《深入微服务》系列就开始了,该系列从微服务的“应用”与“原理”两条线路进行推进,让咱大家伙明白微服务内部到底是个啥。好了不吹牛了,烂尾就打脸了。

该篇代码:

https://github.com/miaogaolin/gofirst/tree/main/example/helloworld

学到什么

  1. 什么是 gRPC ?

  2. 什么是 Protobuf ?

  3. 如何搭建 gRPC 环境?

  4. 如何应用 gRPC ?

什么是 gRPC

gRPC 是基于 Http/2 协议的开源远程过程调用系统。

远程过程调用,即 RPC (Remote Procedure Calls) ,它实现了像本地一样调用远程的方法,而不考虑底层的具体实现细节。

而为了远程调用时数据传输的更高效,选择了 Protobuf,平常我们使用较多的就是 JSON、XML。

gRPC 调用模型

20231208111284.webp

一个完整的调用过程如下:

  1. 假如客户端 (Client) 调用服务端 (Server) 上的 A 方法,则发起 RPC 调用。

  2. 对请求信息使用 Protobuf 进行对象序列化压缩。

  3. 服务端接收到请求后,解码请求体,进行业务逻辑处理并返回。

  4. 对响应结果使用 Protobuf 进行对象序列化压缩。

  5. 客户端接受到服务端响应,解码请求体,并唤醒正在等待 A 方法响应的客户端调用。

什么是 Protobuf

Protobuf (Protocol buffers) 和 Go 都是一个亲妈,来自于 Google。它用于序列结构化数据,提供了一种相比 XML、JSON 格式,会更小、更快、操作更简单的方法。

先简单对比下。

JSON

  
{
  
  "name": "laomiao",
  
  "id": 1,
  
  "email": "2825873215@qq.com"
  
}
  

Protobuf

  
message Person {
  
  string name = 1;
  
  int32 id = 2;
  
  string email = 3;
  
}
  

该信息存储于后缀为 proto 的文件中,它只提供格式,而不存储数据。在使用时,用 Protobuf 编译工具将该文件编译成你所使用的语言。

例如,在 Go 语言中,会根据该信息生成编译后的 Go 文件,message Person 会对应到 struct Person 上,并提供了序列化结构体的方法。

本篇不会详细讲解 Protobuf 定义语法,只会把用到的进行简单描述,详细的前往:

https://developers.google.com/protocol-buffers/docs/proto3

编译工具

将 Proto 文件编译成 Go 代码需要两个工具:

  • protoc 工具,c++ 语言实现。

  • protoc-gen-go 插件,go 语言实现,给 protoc 工具提供插件,生成 go 代码。

gRPC 系统也要基于 Proto 文件生成相关代码,因此还需要一个工具:

  • protoc-gen-go-grpc 插件,go 语言实现,配合上面两个工具。

下来开始安装这些工具。

protoc 工具安装

1. 下载

地址:https://github.com/protocolbuffers/protobuf/releases

根据自己的系统选择对应的版本,我这块选择了 Win64。

20231208111222.png

2. 环境变量

先解压,将整个目录放置到你想要的位置,再把 bin 目录加入环境变量,完成后在控制台输入命令。

  
$ protoc --version
  
libprotoc 3.15.8
  

成功搞定!

插件安装

  
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
  
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
  

安装完成后,会在 $GOPATH/bin 目录下生成两个文件,记得这个目录也要加入环境变量。

编写 Proto

创建 "helloword.proto" 文件。

  
syntax = "proto3";
  

  
option go_package = "github.com/miaogaolin/gofirst/example/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;
  
}
  

解释如下:

  • 第一行,proto3 声明版本号为 3,不写时,默认版本为 2。

  • go_package 定义包路径。

  • Greeter 为 rpc 服务,远程调用的方法为 SayHello。

  • HelloRequest 用于 SayHello 方法参数。

  • HelloReply 用于 SayHello 方法返回。

  • name 和 message 后的数字 1 是给字段分配的唯一编号,如果新增加字段就给分配 2、3、4 等等。

生成代码

工具装好、proto 文件编写好,下来就开始使用工具生成代码。

  
protoc --go_out=. --go_opt=paths=source_relative \
  
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
  
    helloworld.proto
  
  • go_out:生成 go 文件(不含 rpc 代码)的输出目录。

  • go_opt=paths=source_relative:生成的 go 文件和 proto 源文件在同一目录,如果 go_out 定义为其它目录,该设置无效。

  • go-grpc_out:rpc 的 go 文件输出目录。

  • go-grpc_opt=paths=source_relative:生成 rpc 的 go 文件和 proto 源文件在同一目录,如果 go-grpc_out 定义为其它目录,该设置无效。

运行后,会在当前目录下产生两个文件:

  • helloworld.pb.go:数据类型代码。

  • helloworld_grpc.pb.go:rpc 相关代码,其中会产生 GreeterClientGreeterServer 两个接口,一会要实现。

远程调用

要实现 gRPC 的远程调用,就要分别实现 “客户端” 和 “服务端” 代码,下来我会给出关键性代码的说明,完整的前往 Github。

1. 服务端

  
// example/hellworld/server/main.go
  

  
// 实现服务端 GreeterServer 接口
  
type server struct {
  
  // 必须嵌套
  
	// 包含了 GreeterServer 接口其它方法实现
  
	pb.UnimplementedGreeterServer
  
}
  

  
// 远程调用方法的具体逻辑
  
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  
	 log.Printf("Received: %v", in.GetName())
  
	 return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
  
}
  

  
func main() {
  
	// 监听端口,用于接受客户端的请求
  
	lis, err := net.Listen("tcp", port)
  
	
  
	...
  

  
	// 服务端实例化
  
	s := grpc.NewServer()
  
	// 将具体的实现注册到服务端
  
	pb.RegisterGreeterServer(s, &server{})
  

  
	// 阻塞等待
  
	if err := s.Serve(lis); err != nil {
  
		log.Fatalf("failed to serve: %v", err)
  
	}
  
}
  

主线流程:

  1. 监听端口;

  2. 实例化服务端;

  3. 将具体实现进行注册;

  4. 阻塞等待。

2. 客户端

实现好了服务端,下来客户端只需要远程调用就行。

  
// example/hellworld/client/main.go
  

  
func main() {
  
   // 连接服务端
  
   // grpc.WithInsecure() 跳过安全验证,即明文传输
  
   // grpc.WithBlock() 等待与服务端握手成功
  
   conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
  
  
  
	 ...
  

  
   // 客户端实例化
  
   c := pb.NewGreeterClient(conn)
  

  
	 ...
  

  
   // 连接超时设置
  
   ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  
   defer cancel()
  

  
   // 远程调用
  
   r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
  
   if err != nil {
  
      log.Fatalf("could not greet: %v", err)
  
   }
  
	 // 返回消息
  
   log.Printf("Greeting: %s", r.GetMessage())
  
}
  

主线流程:

  1. 连接服务端;

  2. 实例化客户端;

  3. 开始调用;

  4. 获取返回消息。

3. 运行

先执行服务端代码,再执行客户端代码。

服务端

  
$ go run main.go
  
2021/11/04 11:28:19 server listening at [::]:50051
  

客户端

  
$ go run main.go
  
2021/11/04 11:30:17 Greeting: Hello world
  

客户端接收到了服务端的响应,一切完成!

参考