沙滩星空的博客沙滩星空的博客

Go微服务之gRPC远程调用框架

protobuf 简介

protobuf 全称: Protocol Buffers, 是Google公司开发的一种数据类型和结构描述语言。是一种数据传输格式,网络数据传输数据,可用 json,也可用 protobuf

protobuf 很适合做数据存储或 RPC 数据交换格式,它序列化出来的数据量少再加上以 K-V 的方式来存储数据,对消息的版本兼容性非常强,可用于通讯协议、数据存储等领域的 语言无关、平台无关、可扩展的序列化结构数据格式

开发者可以通过Protobuf附带的工具,生成代码并实现将结构化数据序列化的功能。

安装 protoc 命令工具

protoc 命令,用来把后缀为 .proto 的文件(文件为不同编程语言通用的protobuf数据格式定义文件),编译成特定语言(C++, C#, Dart, Go, Java, Kotlin, Python, Objective-C, PHP, Ruby)的源文件(如.c, .go, .php, .py)。使得特定的编程语言可以直接调用,以解码或编码 protobuf 数据格式.

下载 protoc 命令工具 https://github.com/protocolbuffers/protobuf/releases

复制可执行文件到 环境变量目录. 如 $GOPATH/bin 目录

安装 protoc-gen-go 插件

前面提到,我们可以用 protoc 命令来编译 .proto 文件为特定语言的源文件。
为了支持编译为 go语言,需要安装 protoc-gen-go 插件.

使用 go install 命令下载 protoc-gen-go 插件:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

如此,在 $GOPATH/bin 下可见 protoc-gen-go.exe(linux) 或 protoc-gen-go.exe(windows) 文件.

后面执行 protoc 命令会自动调用 protoc-gen-go 插件

proto 文件介绍

字段规则

message User {
  string username = 1;
  int32 age = 2;
  optional string password = 3;
  repeated string address = 4;
}
  • required:消息体中必填字段,不设置会导致编解码异常
  • optional: 消息体中可选字段
  • repeated: 消息体中可重复字段,重复的值的顺序会被保留.在go中重复的会被定义为切片。

字段映射

protoGoPythonC++
doublefloat64floatdouble
floatfloat32floatfloat
bytes[]bytestrstring
stringstringstr/unicodestring

proto3 数据类型

默认值

protobuf3 删除了 protobuf2 中用来设置默认值的 default 关键字,取而代之的是protobuf3为各类型定义的默认值,也就是约定的默认值,如下表所示:

  • bool false
  • 整型 0
  • string 空字符串 ""
  • 枚举 第一个枚举元素的值,因为Protobuf3强制要求第一个枚举元素的值必须是0,所以枚举的默认值就是0
  • message 不是null,而是DEFAULT_INSTANCE

标识号

在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[0,2^29-1]范围内的一个整数。

message Person { 

  string name = 1;  // (位置1)
  int32 id = 2;  
  optional string email = 3;  
  repeated string phones = 4; // (位置4)
}

嵌套消息

message PersonInfo {
    message Person {
        string name = 1;
        int32 height = 2;
        repeated int32 weight = 3;
    } 
    repeated Person info = 1;
}

message PersonMessage {
    PersonInfo.Person info = 1;
}

protobuf数据格式的编码和解码

创建测试项目

创建目录 testProto, 进入该目录,使用命令 go mod init testProto 初始化测试项目

编辑.proto源文件

在测试项目中,创建 protos 目录,在其中创建 .proto文件

vim user.proto:

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
//option go_package = "path;name"; ath 表示生成的go文件的存放地址,会自动生成目录的
// name 表示生成的go文件所属的包名
option go_package="../service";
// 指定等会文件生成出来的package
package service;

message User {
  string username = 1;
  int32 age = 2;
}

使用 protoc 命令生成go源码文件

在测试项目中,创建 service 目录,并执行命令生成go版本的protoc源码文件

# 编译user.proto之后输出到service文件夹
protoc --go_out=service protos/user.proto

以上命令在 service目录 生成 user.pb.go 文件

测试

测试项目中,编写 main.go文件,如下所示:

package main

import (
    "fmt"
    "google.golang.org/protobuf/proto"
    "testProto/service"
)

func main()  {
    user := &service.User{
        Username: "mszlu",
        Age: 20,
    }
    //转换为protobuf
    marshal, err := proto.Marshal(user)
    if err != nil {
        panic(err)
    }
    newUser := &service.User{}
    err = proto.Unmarshal(marshal, newUser)
    if err != nil {
        panic(err)
    }
    fmt.Println(newUser.String())
}

运行命令:

# 解决包依赖
go mod tidy
# 运行
go run .
# 输出
 username:"mszlu"  age:20

测试项目结构如下:

├── go.mod
├── go.sum
├── main.go
├── protos
│   └── user.proto
└── service
    └── user.pb.go

gRPC 实例的Go源码示范

  1. 下载Go版本的 gRPC 包: go get google.golang.org/grpc
  2. protos 目录创建文件: product.proto
  3. protos 目录执行命令: protoc --go_out=./ --go-grpc_out=./ product.proto

注: --go_out=plugins=grpc:[输出目录] 命令选项已被淘汰,用 --go-grpc_out=[输出目录] 选项代替。

--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC

gRPC的客户端与服务器端必须使用同一个.proto文件

客户端调用输出: 调用gRPC方法成功,ProdStock = 66
服务端显示: 收到一个 grpc 请求,请求参数: prod_id:233

文件源码如下所示:

product.proto 文件:

// 这个就是protobuf的中间文件

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
option go_package="../service";

// 指定等会文件生成出来的package
package service;

// 定义request model
message ProductRequest{
    int32 prod_id = 1; // 1代表顺序
}

// 定义response model
message ProductResponse{
    int32 prod_stock = 1; // 1代表顺序
}

// 定义服务主体
service ProductService{
    // 定义方法
    rpc GetProductStock(ProductRequest) returns(ProductResponse);
}

服务端文件: main.go

package main

import (
    "context"
    "log"
        "fmt"
    "net"
    "testProto/service"

    "google.golang.org/grpc"
)

type ProductServer struct{}

func (p ProductServer) GetProductStock(context context.Context, request *service.ProductRequest) (*service.ProductResponse, error) {
    fmt.Println("收到一个 grpc 请求,请求参数:", request)
    response := service.ProductResponse{ProdStock: 66}
    return &response, nil
}

func main() {
    server := grpc.NewServer()
    service.RegisterProductServiceServer(server, ProductServer{})

    listener, err := net.Listen("tcp", ":8002")
    if err != nil {
        log.Fatal("服务监听端口失败", err)
    }
    _ = server.Serve(listener)
}

客户端: 复制服务端项目,更改 main.go:

package main

import (
    "context"
    "fmt"
    "log"
    "testProto/service"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    // 1. 新建连接,端口是服务端开放的8002端口
    // 没有证书会报错
    conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatal(err)
    }

    // 退出时关闭链接
    defer conn.Close()

    // 2. 调用Product.pb.go中的NewProdServiceClient方法
    productServiceClient := service.NewProductServiceClient(conn)

    // 3. 直接像调用本地方法一样调用GetProductStock方法
    resp, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
    if err != nil {
        log.Fatal("调用gRPC方法错误: ", err)
    }

    fmt.Println("调用gRPC方法成功,ProdStock = ", resp.ProdStock)
}

gRPC官方文档 https://grpc.io/docs/languages/go/quickstart/
gRPC教程 — 第一章 https://blog.csdn.net/Mr_XiMu/article/details/124852670
Go中的gRPC入门教程详解 https://www.jb51.net/article/242787.htm
未经允许不得转载:沙滩星空的博客 » Go微服务之gRPC远程调用框架

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址