当前位置: 首页 > article >正文

【go微服务】跨语言RPC接口工具--protobuf语法与原理解读以及应用实战

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言微服务
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • rpc接口工具-protobuf
    • 1. ProtoBuf认识与使用
      • 1.1 protobuf简介
      • 1.2 protobuf的安装
        • 1)下载protobuf
        • 2)安装protoc
        • 3)安装protobuf的go语言插件
    • 2. protobuf 简单语法
    • 3. protobuf高级用法
      • 3.1 message嵌套
      • 3.2 repeated关键字
      • 3.3 默认值
      • 3.4 enum关键字
      • 3.5 oneof关键字
    • 4. 定义RPC服务
    • 5. 定义proto文件
      • 5.1 创建个pb目录
      • 5.2 protobuf基本编译
      • 5.3 添加rpc服务
    • 6. proto文件相互引用
      • 6.1 引入本地的proto
      • 6.2 如何引入第三方proto文件

rpc接口工具-protobuf

1. ProtoBuf认识与使用

1.1 protobuf简介

Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。
它很适合做数据存储或 RPC 数据交换格式。
可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。
这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。

需要了解两点

  1. protobuf是类似与json一样的数据描述语言(数据格式)
  2. protobuf非常适合于RPC数据交换格式

接着我们来看一下protobuf的优势和劣势:
优势:
1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速度

劣势:
1:应用不够广(相比xml和json)
2:二进制格式导致可读性差
3:缺乏自描述

1.2 protobuf的安装

1)下载protobuf

github官网 https://github.com/protocolbuffers/protobuf
方法一:===> git clone https://github.com/protocolbuffers/protobuf.git

方法二:===> 或者将准备好的压缩包进行拖入
解压到$GOPATH/src/github.com/protocolbuffers/下面
unzip protobuf.zip

2)安装protoc

Linux:apt install -y protobuf-compiler 。-y等价于–yes,如果安装出现一些“确认提示,需要你输入yes或no,-y会自动帮你输入yes避免安装流程被中断
Mac:brewinstall protobuf
Windows:到官网上下载最新版的编译好的protoc可执行文件protoc-xxx-win64.zip,解压后有个bin目录,把它放到环境变量PATH里去,这样就可以在任意目录下使用protoc命令了。此方法同样适用于其他操作系统
这里,我们安装Windows版的
在这里插入图片描述

解压后
在这里插入图片描述

把这个目录放到系统path里面
在这里插入图片描述

在这里插入图片描述

查看版本,表示安装成功
在这里插入图片描述

首先,我们可以在终端运行以下命令
protoc --help
在这里插入图片描述

我们发现其实参数 -IPATH 就是代表 -I,所有这个参数就是代表:
-I(-IPATH)指定要在其中搜索导入(import)的目录。可指定多次,目录将按顺序搜索。如果没有给出,则使用当前工作目录。
如:protoc -I=$GOPATH/src --go_out=. hello.proto
说明如果 hello.proto 里面 import 相应文件,会在 gopath 目录下的 src 目录去搜索相应的文件。
在Windows中使用-I 要使用绝对路径
在这里插入图片描述

3)安装protobuf的go语言插件

golang官方工具
在这里插入图片描述

https://github.com/golang/protobuf
在这里插入图片描述

下载,编译:

go get -u google.golang.org/protobuf
go install google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

执行完go install之后,会在GOPATH的bin下生成两个可执行文件
在这里插入图片描述

当然除了官方的安装之外,还有第三方的库可以使用
gogofast是在官方的版本中之上进行了封装
https://github.com/gogo/protobuf
在这里插入图片描述

go get github.com/gogo/protobuf
go install github.com/gogo/protobuf/protoc-gen-gofast
go install github.com/gogo/protobuf/protoc-gen-gogofast
go install github.com/gogo/protobuf/protoc-gen-gogofaster
go install github.com/gogo/protobuf/protoc-gen-gogoslick

2. protobuf 简单语法

参考文档: https://protobuf.dev/programming-guides/proto3/
首先让我们看一个非常简单的例子。

syntax = "proto3";             //指定版本信息,不指定会报错
package pb;                //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
    //    名字
    string name = 1;
    //    年龄
    int32  age = 2 ;
}

enum test{
    int32 age = 0;
}

protobuf消息的定义(或者称为描述)通常都写在一个以 .proto 结尾的文件中。
该文件的第一行指定正在使用proto3语法:如果不这样做,协议缓冲区编译器将假定正在使用proto2。这也必须是文件的第一个非空的非注释行。
第二行package指明当前是pb包(生成go文件之后和Go的包名保持一致)
最后message关键字定义一个Person消息体,类似于go语言中的结构体,是包含一系列类型数据的集合。
许多标准的简单数据类型都可以作为字段类型,包括boolint32floatdouble,和string。也可以使用其他message类型作为字段类型。
在message中有一个字符串类型的value成员,该成员编码时用1代替名字。我们知道,在json中是通过成员的名字来绑定对应的数据,
但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据,因此Protobuf编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。

message的格式说明
消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式:
//注释格式 注释尽量也写在内容上方
(字段修饰符)数据类型 字段名称 = 唯一的编号标签值;

唯一的编号标签:代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。
这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。
需要说明的是标签在1到15范围的采用一个字节进行编码,所以通常将标签1到15用于频繁发生的消息字段。
编号标签大小的范围是1到2的29次。19000-19999是官方预留的值,不能使用。
注释格式:向.proto文件添加注释,可以使用C/C++/java/Go风格的双斜杠(//) 语法格式或者/*.....*/
在这里插入图片描述

message常见的数据类型与go中类型对比
在这里插入图片描述

3. protobuf高级用法

protobuf除了上面的简单类型还有一些复杂的用法,如下:

3.1 message嵌套

messsage除了能放简单数据类型外,还能存放另外的message类型,如下:

syntax = "proto3";                         //指定版本信息,不指定会报错
package pb;                        //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
    //    名字
    string name = 1;
    //    年龄
    int32  age = 2 ;
    //定义一个message
    message PhoneNumber {
    string number = 1;
    int64 type = 2;
    }
    PhoneNumber phone = 3;
}

3.2 repeated关键字

repeadted关键字类似与go中的切片,编译之后对应的也是go的切片,用法如下:

syntax = "proto3";                         //指定版本信息,不指定会报错
package pb;                        //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
    //    名字
    string name = 1;
    //    年龄
    int32  age = 2 ;
    //定义一个message
    message PhoneNumber {
    string number = 1;
    int64 type = 2;
    }
    
    repeated PhoneNumber phone = 3;
}

3.3 默认值

解析数据时,如果编码的消息不包含特定的单数元素,则解析对象对象中的相应字段将设置为该字段的默认值。不同类型的默认值不同,具体如下:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于bool,默认值为false。
  • 对于数字类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • repeated字段默认值是空列表
  • message字段的默认值为空对象

3.4 enum关键字

在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表。
比如说,电话号码字段有个类型,这个类型可以是,home,work,mobile。我们可以通过enum在消息定义中添加每个可能值的常量来非常简单的执行此操作。实例如下:

syntax = "proto3";                         //指定版本信息,不指定会报错
package pb;                        //后期生成go文件的包名,目前这个不能用了。使用option
//message为关键字,作用为定义一种消息类型
message Person{
    //    名字
    string name = 1;
    //    年龄
    int32  age = 2 ;
    //定义一个message
    message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
    }
    
    repeated PhoneNumber phone = 3;
}

//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
}

如上,enum的第一个常量映射为0,每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:

  • 必须有一个零值,以便我们可以使用0作为数字默认值。
  • 零值必须是第一个元素,以便与proto2语义兼容,其中第一个枚举值始终是默认值。

enum还可以为不同的枚举常量指定相同的值来定义别名。如果想要使用这个功能必须讲allow_alias选项设置为true,负责编译器将报错。示例如下:

syntax = "proto3";                         //指定版本信息,不指定会报错
package pb;                        //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
    //    名字
    string name = 1;
    //    年龄
    int32  age = 2 ;
    //定义一个message
    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phone = 3;
}

//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
    //如果不设置将报错
    option allow_alias = true;
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
    Personal = 2;
}

3.5 oneof关键字

如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用oneof功能,示例如下:

message Person{
    //    名字
    string name = 1;
    //    年龄
    int32  age = 2 ;
    //定义一个message
    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phone = 3;
    oneof data{
        string school = 5;
        int32 score = 6;
    }
}

4. 定义RPC服务

如果需要将message与RPC一起使用,则可以在.proto文件中定义RPC服务接口,protobuf编译器将根据你选择的语言生成RPC接口代码。示例如下:
//service等同于go语言的接口,接口里面可以定义多个方法

service StudentService{
  rpc GetStudentInfo(Request) returns (Student); //指定函数名,入参类型,出参类型,入参和出参有且仅有一个
}

大部分protobuf的语法就是这样,其他想学习的可以参考官方文档,语法写完之后,让我们编译一下,然后通过代码试用一下。

5. 定义proto文件

5.1 创建个pb目录

然后创建.proto结尾的文件
在这里插入图片描述

// 默认是 proto2
syntax = "proto3";

// 指定所在包包名
//package pb;  目前这个不能用了,使用option go_package指定

//指定生成go文件所在路径(该路径不存在会自动创建),.指的是--go_out指定的路径。即go文件实际的存放路径为./myrpc/pb/student01
//分号后面指的是生成文件的package名
option go_package="./student01;pb";

// 定义枚举类型
enum Week {
  Monday = 0;   // 枚举值,必须从 0 开始.
  Tuesday = 1;
}

// 定义消息体
message Student {
  int32 age = 1;  // 可以不从1开始, 每个message中字段的编号是不能重复. -- 不能使用 19000 - 19999
  string name = 2;
  People p = 3;
  repeated int32 score = 4;  // 数组
  // 枚举
  Week w = 5;
  // 联合体
  oneof data {
    string teacher = 6;
    string class = 7;
  }
  //蛇形形式的变量,转化成go代码后变成驼峰形式
  int64 create_at=8;
}

// 消息体可以嵌套
message People {
  int32 weight = 1;
}

message Request{
  string StudentId=1;
  string TraceId=2;
}

//service等同于go语言的interface,接口里面可以定义多个方法
service StudentService{
  rpc GetStudentInfo(Request) returns (Student); //指定函数名,入参类型,出参类型,入参和出参有且仅有一个
}

5.2 protobuf基本编译

protobuf编译是通过编译器protoc进行的,通过这个编译器,我们可以把.proto文件生成go,Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,生成命令如下:
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto

  1. –proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用–proto_path,它们将会顺序的被访问并执行导入。
  2. –go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹,go_out指定的目录必须是已经存在的
  3. 允许使用 protoc --go_out=./ *.proto 的方式一次性编译多个 .proto 文件
  4. go语言编译时,protobuf 编译器会把 .proto 文件编译成 .pd.go 文件

一般在使用的时候我们都是使用下面这种简单的命令:
protoc --go_out=./ *.proto

编译当前文件夹下的所有.proto文件,并把生成的go文件放置在当前文件夹下。
我们先来编译一个最简单的的proto文件,编译之后会得到一个如下一个go文件,如下:

执行编译
在这里插入图片描述

查看生成的文件,将protobuf的语法转化成go的语法
看下路径,包名
在这里插入图片描述

生成的文件,建议别编辑
在这里插入图片描述

gogofaster序列化最快
protoc --gogofaster_out=./ *.proto

使用 gogofaster 插件生成代码
在项目根目录下运行以下命令:
protoc --gogofaster_out=plugins=grpc:./ *.proto

–gogofaster_out 指定生成代码的输出目录,plugins=grpc 表示启用 gRPC 插件

5.3 添加rpc服务

语法:

service 服务名 {
    rpc 函数名(参数:消息体) returns (返回值:消息)
}
message People {
    string name = 1;
}
message Student {
    int32 age = 2;
}
例:
service hello {
    rpc HelloWorld(People) returns (Student);
}

然后我们给这个.proto文件中添加一个RPC服务,再次进行编译,发现生成的go文件没有发生变化。
这是因为世界上的RPC实现有很多种,protoc编译器并不知道该如何为HelloWorld服务生成代码。不过在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码:
默认情况下,protobuf,编译期间,不编译服务。 要想使之编译。 需要使用 gRPC。
使用的编译指令为:
protoc --go_out=plugins=grpc:./ *.proto
新版本使用这个命令报错
在这里插入图片描述

在较新版本的 Protocol Buffers (protobuf) 中,protoc-gen-go 插件的用法已经发生了变化。在 protobuf 3.6.0 版本中,引入了模块化插件系统,这意味着你需要使用不同的方式来生成 Go 代码。

新版本命令
使用 protoc 命令时,确保指定 protoc-gen-go 和 protoc-gen-go-grpc:
protoc --go_out=. --go_opt=paths=source_relative
–go-grpc_out=. --go-grpc_opt=paths=source_relative
your_proto_file.proto

需要借助之前编译的protoc-gen-go-grpc.exe
在这里插入图片描述

执行编译指令:
protoc --go_out=./ --go-grpc_out=./ *.proto

查看生成的go文件
在这里插入图片描述

后续都借助proto文件来生成grpc服务,不用手写grpc服务了

6. proto文件相互引用

有时候,我们需要在一个proto文件中引入另一个proto文件中的变量,结构体,函数等等
此时就要借助package指定的包名

6.1 引入本地的proto

比如我们想在myproto2.proto中引入myproto1.proto中的Student
在这里插入图片描述

需要在myproto2.proto中导入myproto.proto
并且使用myproto.proto中package指定的包名pb1.Student即可
在这里插入图片描述

编译之后,需要修改包名路径
引入本地包,要从go mod中的module名开始
在这里插入图片描述

但是我们生成的引入路径是从pb开始
在这里插入图片描述

我们可以手动修改,就不报错
在这里插入图片描述

如果想要编译时就能正确引入对路径,可以在编译时加个参数

--gogofaster_opt=Mpb/pb1/myproto1.proto=jingtian/myrpc/pb/pb1

–gogofaster_opt=M 是固定的,后面是引入的proto文件路径,等于后面是引入的proto文件相对于项目的目录路径

protoc -I="F:/goworks\src/jingtian/myrpc" --gogofaster_opt=Mpb/pb1/myproto1.proto=jingtian/myrpc/pb/pb1 --gogofaster_out=./pb/pb2 --proto_path=./pb/pb2/ myproto2.pro

这样,生成的go代码引入的路径就对了
在这里插入图片描述

6.2 如何引入第三方proto文件

比如我们想要引入gogofaster的这个timestamp.proto中的Timestamp这个message
在这里插入图片描述
在这里插入图片描述

可以借助写多个-I 来指定gogofaster的这个路径
在这里插入图片描述

-I指定google前面的绝对路径
在这里插入图片描述

 protoc -I="F:/goworks\src/jingtian/myrpc" -I="F:/goworks/pkg/mod/github.com/gogo/protobuf@v1.3.2/protobuf" --gogofaster_opt=Mpb/pb1/myproto1.proto=jingtian/myrpc/pb/pb1 --gogofaster_out=./pb/pb2 --proto_path=./pb/pb2/ myproto2.proto

此时import会出错
在这里插入图片描述

那是因为我们在proto文件中引入的是这个路径
在这里插入图片描述

此时我们想要引用正确的路径,需要知道该proto文件生成的go代码路径
我们发现生成的go代码在这个路径
在这里插入图片描述

我们先手动修改下
github.com/gogo/protobuf/types
在这里插入图片描述

因此,我们还是需要通过–gogofaster_opt=M 来指定go代码路径

protoc -I="F:/goworks\src/jingtian/myrpc" -I="F:/goworks/pkg/mod/github.com/gogo/protobuf@v1.3.2/protobuf" --gogofaster_opt=Mpb/pb1/myproto1.proto=jingtian/myrpc/pb/pb1 --gogofaster_opt=Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types --gogofaster_out=./pb/pb2 --proto_path=./pb/pb2/ myproto2.proto

此时生成的路径就对了
在这里插入图片描述


http://www.kler.cn/a/611747.html

相关文章:

  • [MRCTF2020]套娃
  • 实战 | 基于 SpringBoot + UniApp 打造国际版打车系统:架构设计与性能优化全解析
  • SQL优化 | OceanBase是否遵循最左匹配原则?(三)
  • MDC的原理是什么?
  • 计算机二级(C语言)考试高频考点总汇(二)—— 控制流、函数、数组和指针
  • 向量数据库的适用场景与局限性分析
  • Java爬虫如何解析返回的JSON数据?
  • Dynamic Soft Contrastive Learning for Time Series Anomaly Detection 解读
  • 【2025】基于springboot+uniapp的企业培训打卡小程序设计与实现(源码、万字文档、图文修改、调试答疑)
  • 套接字Socket
  • 算法-深度优先搜索
  • ubuntu单机部署redis集群
  • HarmonyOS NEXT 鸿蒙中关系型数据库@ohos.data.relationalStore API 9+
  • IP 分片重组与 TCP 会话重组
  • 二分查找模板--从题目中讲解三大二分模板
  • [vue]计算属性
  • WPF ContentPresenter详解2
  • 网损仪详解
  • 比R版本快几十倍| Pyscenic单细胞转录因子预测
  • nVisual对接企业微信实现机房设备与连接变更的自动化审批