什么是gRPC,如何使用protoc,buf工具集,这些你还不会吗?

gRPC 简介

gRPC是一个高性能、开源的远程过程调用(RPC)框架。它允许客户端和服务器之间进行通信,支持多种编程语言,并且能够在多种环境中部署。

特点:

  • 多语言支持:gRPC 支持多种编程语言,包括但不限于:C++, Java, Python, Go, Ruby, C#, Node.js, PHP, 和 Objective-C。这使得 gRPC 可以在跨语言的项目中使用。
  • 基于HTTP/2:gRPC 使用 HTTP/2 作为其传输协议。这带来了许多优点,包括双向流、头部压缩、单个TCP连接中的多路复用请求等。
  • 协议缓冲(Protocol Buffers):gRPC 使用 Protocol Buffers(protobufs)作为其接口定义语言(IDL)和默认的消息序列化格式。Protobuf 是一种高效的二进制序列化机制,能够显著减少消息的大小和序列化/反序列化的时间。
  • 双向流:gRPC 支持四种主要的RPC模式:Unary RPC、Server Streaming RPC、Client Streaming RPC 和 Bidirectional Streaming RPC。这使得它能够处理各种复杂的通信模式。
  • 负载均衡、跟踪、健康检查和认证:gRPC 支持许多现代微服务架构所需的特性,包括负载均衡、分布式跟踪、健康检查和多种认证机制。

gRPC 与 IDL

什么是 IDL?

接口定义语言(IDL, Interface Definition Language)是一种用于定义软件组件间接口的描述语言。它允许开发者在不同语言和平台间描述和传递数据结构和服务接口。

在 gRPC 中,接口定义语言(IDL)是用于定义服务和消息结构的关键部分。gRPC 使用 Google 的 Protocol Buffers(protobufs)作为其默认的 IDL 和消息序列化格式。gPRC 的 IDL 是与语言和平台无关的,通过一个编译的过程,生成各个语言的代码。

这里我们将详细解释一下 gRPC 和 IDL 的关系,以及如何使用 Protocol Buffers 来定义 gRPC 服务。

关系:gRPC 先定义了 IDL。而后 gRPC 需要一门编程语言作为 IDL 落地的形式,因此使用了 protobufs。

什么是Protocol Buffers?

Protocol Buffers 是一种由 Google 开发的语言中立、平台无关、可扩展的机制,用于序列化结构化数据。它包括以下几个部分:

  • 消息类型定义:用于定义数据结构。
  • 服务定义:用于定义远程过程调用(RPC)接口。
  • 编译器(protoc):将 protobuf 定义编译成特定语言的代码。

优势:

  1. 高效的二进制格式
  • 紧凑性:Protobuf 使用紧凑的二进制格式进行数据序列化,比 JSON 和 XML 的文本格式占用更少的空间。它可以显著减少数据传输所需的带宽。
  • 速度:由于其二进制格式,Protobuf 的序列化和反序列化速度非常快,适合性能要求高的应用。
  1. 跨语言支持:Protobuf 支持多种编程语言,包括但不限于:C++, Java, Python, Go, Ruby, C#, JavaScript, PHP, 和 Objective-C。这使得 Protobuf 能够在跨语言的项目中使用,确保不同语言之间的数据交换无缝进行。
  2. 清晰的接口定义:使用 .proto 文件定义数据结构和服务接口,使得接口清晰明了。开发者可以通过读取 .proto 文件快速了解接口和数据结构的定义。这种接口定义语言(IDL)不仅定义了数据结构,还定义了数据的序列化和反序列化方式。
  3. 向后兼容性和扩展性:Protobuf 提供了良好的向后兼容性。开发者可以在不破坏现有代码的情况下对数据结构进行扩展。新版本的消息可以包含额外的字段,而旧版本的消息仍然可以正确解析。通过为每个字段分配唯一的标签(tag),Protobuf 确保了字段的顺序和存在与否不会影响数据的解析。
  4. 强类型:Protobuf 是强类型的,这意味着在定义数据结构时需要指定每个字段的类型。这种强类型检查可以在编译时捕获许多潜在的错误,提高代码的可靠性。
  5. 自动代码生成:Protobuf 提供了编译器(protoc),它可以根据 .proto 文件自动生成多种语言的代码。这减少了手工编写序列化和反序列化代码的需求,提高了开发效率。自动生成的代码包括数据类、序列化和反序列化方法,以及对于 gRPC 服务的客户端和服务器代码。
  6. 支持嵌套和复合类型:Protobuf 支持嵌套消息和复合类型,使得它能够表达复杂的数据结构。开发者可以在一个消息中包含其他消息类型,从而构建复杂的数据模型。
  7. 灵活的序列化选项:Protobuf 提供了灵活的序列化选项,包括可选字段、重复字段(类似于数组或列表)和映射(键值对)。这些特性使得 Protobuf 可以处理各种复杂的数据结构和需求。

语法入门

Protobuf使用.proto作为文件后缀,正常来说,在每一个文件中都要生命本文件的语法版本syntax。一般情况下默认使用syntax=proto3

syntax = "proto3";

如果需要生成 Go 的代码,需要在文件里面加上相关的配置。go_package 选项用于指定生成的 Go 代码的包路径。

syntax = "proto3";

package helloworld;

// ";"前表示生成的 Go 代码的包路径,“;”后表示生成的Go代码的包名
option go_package = "github.com/yourusername/yourproject/helloworld;example";

为什么要使用 go_package 呢?

  • 避免包名冲突:在大型项目或多个项目中,使用 go_package 可以确保生成的代码放置在特定的包路径下,避免包名冲突。
  • 明确代码组织:通过指定包路径,可以使代码的组织更加明确和清晰,有助于维护和管理。
  • 兼容性:在 Go 中,包路径通常与项目的文件结构和版本控制系统(如 GitHub)紧密相关。go_package 选项可以确保生成的代码与 Go 的包路径规则兼容。

文件结构:

yourproject/
├── helloworld/
│   ├── helloworld.proto
│   ├── helloworld.pb.go
│   ├── helloworld_grpc.pb.go
├── main.go

message 定义

message 是一种用于定义数据结构的核心概念。它类似于其他编程语言中的类或结构体,用于描述要序列化和反序列化的数据。每个 message 包含一个或多个字段,每个字段都有一个唯一的编号(标签)和类型。

每个字段都有三要素:类型、名称和标签。标签是一个唯一的整数,用于标识字段。

示例:

syntax = "proto3";

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

字段类型:

  • int32, int64: 有符号整数
  • uint32, uint64: 无符号整数
  • sint32, sint64: 有符号整数,采用 ZigZag 编码
  • bool: 布尔值
  • string: 字符串
  • bytes: 字节序列
  • float, double: 浮点数
  • map:map<keyType, ValueType>
  • 数组:repeat
  • enum:枚举类型

字段默认值:

  • 数字类型(如 int32、float)的默认值是 0
  • 字符串的默认值是空字符串 “”
  • 布尔类型的默认值是 false
  • 枚举类型的默认值是第一个定义的枚举值

service 定义

service 用于定义一组远程过程调用(RPC)方法,这些方法可以在客户端和服务器之间调用。每个 service 包含一个或多个 RPC 方法,每个方法都定义了请求和响应消息类型。
每一个 service 的请求和响应消息类型都是一个 message

示例:

// 定义请求消息
message HelloRequest {
    string name = 1;
}

// 定义响应消息
message HelloReply {
    string message = 1;
}

// 定义 Greeter 服务
service Greeter {
    // 定义 SayHello 方法
    rpc SayHello (HelloRequest) returns (HelloReply);
}

工具安装

安装 protobuf

在MacOS上,我直接使用 HomeBrew 进行安装。

$ brew install protobuf

安装完成之后,查看 protobuf 的版本。

$ protoc --version
$ libprotoc 25.1 #这是我的版本

接下来,讲解一下 protoc 常用的命令。

  1. 指定 proto 文件的路径。如果填写.表示当前目录下。
--proto_path=IMPORT_PATH, -I=IMPORT_PATH

示例:

protoc --proto_path=src --proto_path=include --go_out=outdir src/foo.proto
  1. 生成 go 代码存放路径。如果填写.表示当前目录下。
--go_out=OUT_DIR

示例:

protoc --go_out=. foo.proto
  1. 生成 gRPC 代码 (Go)。
--go-grpc_out=OUT_DIR

示例:

.proto 文件编译成 Go 的 gRPC 代码并输出到指定目录。通常与 --go_out 一起使用。

protoc --go_out=. --go-grpc_out=. foo.proto
  1. 代码生成插件
protoc --plugin=protoc-gen-custom=path/to/custom-plugin --custom_out=. foo.proto

参数说明:

  • –plugin=protoc-gen-NAME=path/to/plugin:指定一个自定义插件,其中 NAME 是插件的名称,path/to/plugin 是插件的可执行文件路径。
  • –NAME_out=OUT_DIR:指定由插件生成的输出代码目录,其中 NAME 是之前指定的插件名称。

示例:
假设你正在使用一个名为 protoc-gen-go 的自定义插件来生成 Go 代码。该插件位于 /usr/local/bin/protoc-gen-go

protoc --plugin=protoc-gen-go=/usr/local/bin/protoc-gen-go --go_out=. example.proto

将使用 protoc-gen-go 插件来生成 Go 代码,并将生成的代码输出到当前目录。

安装 go-gen 和 gRPC 插件

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

以上两条命令安装:

  • protoc-gen-go:生成 Go 代码的插件。
  • protoc-gen-go-grpc:生成 gRPC 代码的插件。

记住,确保你的 GOPATH/bin 目录在系统的 PATH 中,这样你可以直接使用这些插件。

使用工具

  1. 创建 .proto文件
syntax = "proto3";

package example;

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}
  1. 使用 protoc --plugin 生成代码
# 生成 Go 代码
protoc --plugin=protoc-gen-go=$(which protoc-gen-go) --go_out=. example.proto
# 生成 gRPC 代码
protoc --plugin=protoc-gen-go-grpc=$(which protoc-gen-go-grpc) --go-grpc_out=. example.proto

在这个命令中:

  • --plugin=protoc-gen-go=$(which protoc-gen-go):指定使用 protoc-gen-go 插件来生成 Go 代码。
  • --go_out=.:指定生成的 Go 代码输出到当前目录。
  • --plugin=protoc-gen-go-grpc=$(which protoc-gen-go-grpc):指定使用 protoc-gen-go-grpc 插件来生成 gRPC 代码。
  • --go-grpc_out=.:指定生成的 gRPC 代码输出到当前目录。

执行这些命令后,你将在当前目录中看到生成的 .pb.go_grpc.pb.go 文件,这些文件包含了 example.proto 中定义的消息和服务的 Go 代码。

到这里,基本的使用已经介绍完毕了,接下来,将会使用 Buf 这个工具对 protobuf API 进行统一管理。

使用 Buf

我们直接使用 protoc 有如下几个难点:

  • 插件难以管理。
  • protoc 命令不好记,每次都要输入一大堆参数,烦人。
  • 各种文件的 package 的定位简直 要命。

对于以上问题的考虑,我们可以直接使用 Buf 管理。

这是安装地址https://buf.build/docs/installation

首先,在项目的顶级目录下使用 buf config init 初始化项目。这时候,你会看到一个buf.yaml的文件。

buf.yaml

文件主要用于定义项目的整体配置,包括版本控制、模块路径、托管模式、lint 规则和 breaking changes 验证规则等。

以下介绍一下 yaml 文件如何编写:

version: v1
# 定义配置文件的版本。

# 定义输入目录或文件。
# 你可以定义多个目录或文件,Buf 会递归地找到所有 .proto 文件。
# 例如,你可以指定一个单独的目录或多个目录。
build:
  roots:
    - proto
    - third_party

# 定义生成代码的配置。
# 你可以指定多个生成器,每个生成器对应一个输出目录。
# 例如,你可以生成 Go 代码和 gRPC 代码。
# 生成器可以是内置的,也可以是自定义的。
# 例如,你可以使用 protoc-gen-go 和 protoc-gen-go-grpc 插件生成 Go 代码和 gRPC 代码。
generate:
  plugins:
    - name: go
      out: gen/go
    - name: go-grpc
      out: gen/go

# 定义验证规则。
# 你可以指定多个规则,每个规则对应一个验证器。
# 例如,你可以使用 lint 和 breaking 验证器验证你的 Protobuf 文件。
lint:
  use:
    - DEFAULT

breaking:
  use:
    - FILE

详细示例:
文件的目录结构:

my_project/
├── buf.yaml
├── proto/
│   └── example.proto
└── third_party/
    └── google/
        └── api/
            └── annotations.proto

yaml文件编写:

version: v1beta1

build:
  roots:
    - proto
    - third_party

generate:
  plugins:
    - name: go
      out: gen/go
    - name: go-grpc
      out: gen/go

lint:
  use:
    - DEFAULT

breaking:
  use:
    - FILE

构建项目

buf build

buf.gen.yaml

此文件主要用于定义代码生成的配置,包括使用哪些插件来生成代码、代码生成的输出目录和生成选项等。这些配置项专注于实际的代码生成过程。

新建buf.gen.yaml文件,并编写相关的配置:

version: v2
managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: github.com/username/project
plugins:
  # 默认使用最新版本
  # remote 插件的远程地址
  - remote: buf.build/protocolbuffers/go
    # protoc-gen-go 插件
    out: grpcpro/grpc/proto/gen
    opt: paths=source_relative

  - remote: buf.build/grpc/go
    # proto-gen-go-grpc 插件
    out: grpcpro/grpc/proto/gen
    opt: paths=source_relative

inputs:
  # 定义输入目录。Buf 将处理该目录下的所有 .proto 文件
  - directory: grpc

使用命令 buf generate

生成对应的go语言的代码。 注意:在与yaml同级的grpc文件夹下存着我们的proto文件。

下面是对应的项目文件结构:

my_project/
├── buf.yaml
├── grpc/
│   └── example.proto
└── grpcpro/
    └── grpc/
        └── proto/
            └── gen/

区别

  • 用途不同:buf.yaml 用于定义项目的整体配置和验证规则,而 buf.gen.yaml 则专门用于定义代码生成的配置。
  • 配置项不同:buf.yaml 包含版本、模块路径、托管模式、lint 规则和 breaking changes 验证规则等配置项;而 buf.gen.yaml 则包含插件配置、输出目录和生成选项等。
  • 文件作用范围不同:buf.yaml 文件的作用范围较广,影响整个项目的配置和验证;buf.gen.yaml 文件则专注于代码生成。

总结

以上就是关于 gRPC 的简单使用,以及相关的工具集合的使用等。文章整体篇幅较大,并且操作难度偏上,希望广大读者耐心看完,并着手实践。

gRPC是一种高性能、开源的远程过程调用(RPC)框架,由Google开源,支持多种编程语言,包括C++、Java、Python、Go等。通过gRPC,您可以轻松地在客户端和服务器之间定义服务,并在这些服务之间自动生成强类型的客户端和服务器代码。gRPC基于HTTP/2协议,使用protobufProtocol Buffers)作为数据传输格式,具有高效、跨语言、可扩展等特点。 使用gRPC,您需要先定义服务和消息的.proto文件,然后使用该文件生成客户端和服务器代码,最后实现客户端和服务器代码来实现RPC调用。以下是简单的使用步骤: 1. 定义.proto文件 ```protobuf syntax = "proto3"; package hello; service HelloWorld { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } ``` 2. 使用protobuf生成代码 ```bash $ protoc --proto_path=. --go_out=. --go-grpc_out=. hello.proto ``` 3. 实现服务器代码 ```go package main import ( "context" "log" "net" "google.golang.org/grpc" pb "path/to/hello" ) type server struct{} func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloResponse{Message: "Hello " + in.GetName()}, nil } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterHelloWorldServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } ``` 4. 实现客户端代码 ```go package main import ( "context" "log" "os" "time" "google.golang.org/grpc" pb "path/to/hello" ) const ( address = "localhost:50051" defaultName = "world" ) func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewHelloWorldClient(conn) // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } 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()) } ``` 以上代码是使用Go语言实现的一个简单的gRPC服务,您可以根据需要选择其他编程语言实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值