乐趣区

关于思否技术征文:Go微服务一文带你玩转ProtoBuf

前言

在网络通信和通用数据交换等利用场景中常常应用的技术是 JSON 或 XML,在微服务架构中通常应用另外一个数据交换的协定的工具ProtoBuf

ProtoBuf 也是咱们做微服务开发,进行 Go 进阶实战中,必知必会的晓得点。

明天就开始第一章内容:《一文带你玩转 ProtoBuf》

5 分钟入门

1.1 简介

你可能不晓得 ProtoBuf,但肯定晓得 json 或者 xml,从肯定意义上来说他们的作用是一样的。

ProtoBuf 全称:protocol buffers,直译过去是:“协定缓冲区”,是一种与语言无关、与平台无关的可扩大机制,用于序列化结构化数据。

和 json\xml 最大的区别是:json\xml 都是基于文本格式,ProtoBuf 是二进制格局。

ProtoBuf 相比于 json\XML,更小(3 ~ 10 倍)、更快(20 ~ 100 倍)、更为简略。

咱们只须要定义一次数据结构,就能够应用 ProtoBuf 生成源代码,轻松搞定在各种数据流和各种语言中写入、读取结构化数据。

1.2 装置

倡议大家应用支流版本 v3,这是官网下载地址:https://github.com/protocolbu…

留神,不同的电脑系统安装包是不一样的:

  • Windows 64 位 点这里下载
  • Windows 32 位 点这里下载
  • Mac Intel 64 位 点这里下载
  • Mac ARM 64 位 点这里下载
  • Linux 64 位 点这里下载

小技巧:Mac 查看本人的芯片类型点击左上角的苹果图标,再点击对于本机,就能够查看了。

比方,我的处理器芯片是 intel 的,下载安装包之后是这样的:

bin 目录下的 protoc 是 ProtoBuf 的工具集,下文会重点介绍它的应用。

留神:咱们须要将下载失去的可执行文件 protoc 所在的 bin 目录加到咱们电脑的环境变量中。

Mac 装置小技巧

如果你的 Mac 装置了 brew,装置 ProtoBuf 就更简略了,咱们应用 brew install ProtoBuf 就能够了

1.3 编译 go 语言的工具包

这个 protoc 能够将 proto 文件编译为任何语言的文件,想要编译为 go 语言的,还须要下载另外一个可执行文件

命令是这样的:

go install google.golang.org/ProtoBuf/cmd/[email protected]

或者

go get github.com/golang/protobuf/protoc-gen-go

1.4 编写 proto 代码

上面就编写一个非常简单,然而五脏齐全的 proto 代码,咱们再依据这段代码生成 pb.go 文件。

syntax = "proto3";

package hello;

option go_package = "./;hello";

message Say{
  int64           id    = 1;
  string          hello = 2;
  repeated string word  = 3;
}

1.5 生成 go 代码

生成 go 代码,非常简单,应用上面的命令就能够了。

切换到.proto 文件所在目录

cd proto/demo/

指定 proto 源文件,主动生成代码。

protoc --go_out=. hello.proto

执行下面的命令后,咱们在我的项目中就主动生成了一个 .pb.go 的文件

入门 ProtoBuf 就是这么的简略:通过这几步咱们就实现了 ProtoBuf 的下载、装置、编写了一个 proto 文件,并生成了能用 Go 语言读写 ProtoBuf 的源代码。

咱们再深刻理解一下 probuf 的用法:

10 分钟进阶

上面再带大家深刻理解一下 ProtoBuf 的知识点,防止在开发中踩坑。

小技巧:写 proto 和写 go 最大的区别是须要在结尾增加分号的;,在开发过程中给本人提个醒:如果是写 proto 须要加分号,如果是写 go 不须要加分号。

以咱们下面的 proto 入门代码举例:

1.1 关键字

  • syntax:是必须写的,而且要定义在第一行;目前 proto3 是支流,不写默认应用 proto2
  • package:定义咱们 proto 文件的包名
  • option go_package:定义生成的 pb.go 的包名,咱们通常在 proto 文件中定义。如果不在 proto 文件中定义,也能够在应用 protoc 生成代码时指定 pb.go 文件的包名
  • message:十分重要,用于定义音讯构造体,不必焦急,下文会重点解说

仔细的小伙伴肯定留神到了 message 音讯体中有一个“repeated”关键字,这在咱们写 Go 的时候是没有的。

这是干什么用的呢?上面来具体解答一下:

1.2 数组类型

对于数组类型,和 Java、Go、PHP 等语言中,定义数据类型不一样。

在 ProtoBuf 音讯中定义数组类型,是通过在字段后面减少 repeated 关键词实现,标记以后字段是一个数组。

只有应用 repeated 标记类型定义,就示意数组类型。

咱们来举两个例子:

1. 整数数组:

上面定义的 arrays 示意 int32 类型的数组

message Msg {repeated int32 arrays = 1;}

2. 字符串数组

上面定义的 names 示意字符串数组

message Msg {repeated string names = 1;}

repeated搞懂了,message又是干嘛用的呢?

1.3 音讯

音讯(message),在 ProtoBuf 中指的就是咱们要定义的 数据结构。相似于 Go 中定义构造体。

message 关键词用法也非常简单:

1. 语法

syntax = "proto3";

message 音讯名 {音讯体}

例子:

syntax = "proto3";
 
message Request {
  string query = 1;
  int32  page = 2;
  int32  limit = 3;
}

定义了一个 Request 音讯,这个音讯有 3 个字段,query 是字符串类型,page 和 limit 是 int32 类型。

1.4 字段类型

ProtoBuf 反对多种数据类型,例如:string、int32、double、float 等等,我整顿了一份 ProtoBuf 和 go 语言的数据类型映射表

.proto Type Go Type 应用技巧
double float64 没非凡技巧,记住 float 对应 go 的 float32,double 对应 go 的 float64 就能够了
float float32 没非凡技巧,记住 float 对应 go 的 float32,double 对应 go 的 float64 就能够了
int32 int32 应用变长编码,对于负值的效率很低,如果你的域有可能有负值,请应用 sint64 代替
uint32 uint32 应用变长编码
uint64 uint64 应用变长编码
sint32 int32 应用变长编码,这些编码在负值时比 int32 高效的多
sint64 int64 应用变长编码,有符号的整型值。编码时比通常的 int64 高效。
fixed32 uint32 总是 4 个字节,如果数值都比 228 大的话,这个类型会比 uint32 高效。
fixed64 uint64 总是 8 个字节,如果数值都比 256 大的话,这个类型会比 uint64 高效。
sfixed32 int32 总是 4 个字节
sfixed64 int64 总是 8 个字节
bool bool 严格对应,玩不出其余花色来
string string 一个字符串必须是 UTF- 8 编码或者 7 -bit ASCII 编码的文本。
bytes []byte 能够蕴含任意程序的字节数组

1.5 调配标识号

仔细的小伙伴可能又有疑难了,下面音讯体中的 string query = 1; 这个 1 是什么呢?

这些数字是“调配示意号”:在音讯定义中,每个字段前面都有一个惟一的数字,这个就是标识号。

这些标识号的作用是:用来在音讯的二进制格局中辨认各个字段的,一旦开始应用就不可能再扭转。

留神:调配标识号在每个音讯内惟一,不同的音讯体是能够领有雷同的标识号的。

小技巧:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用 2 个字节。所以应该为那些频繁呈现的音讯元素保留 [1,15]之内的标识号。

1.5.1 保留标识号(Reserved)

小技巧:要为未来有可能增加的、频繁呈现的字段预留一些标识号。

咱们想保留一些标识号,留给当前用,能够应用上面语法:

message Test {reserved 2, 5, 7 to 10; // 保留 2,5,7 到 10 这些标识号}

如果应用了这些保留的标识号,protocol buffer 编译器无奈编译通过,将会输入正告信息。

1.6 将音讯编译成各种语言版本的类库

编译器命令格局:

protoc [OPTION] PROTO_FILES

OPTION 是命令的选项, PROTO_FILES 是咱们要编译的 proto 音讯定义文件,反对多个。

罕用的 OPTION 选项:

  --go_out=OUT_DIR            指定代码生成目录,生成 Go 代码
  --cpp_out=OUT_DIR           指定代码生成目录,生成 C++ 代码
  --csharp_out=OUT_DIR        指定代码生成目录,生成 C# 代码
  --java_out=OUT_DIR          指定代码生成目录,生成 java 代码
  --js_out=OUT_DIR            指定代码生成目录,生成 javascript 代码
  --objc_out=OUT_DIR          指定代码生成目录,生成 Objective C 代码
  --php_out=OUT_DIR           指定代码生成目录,生成 php 代码
  --python_out=OUT_DIR        指定代码生成目录,生成 python 代码
  --ruby_out=OUT_DIR          指定代码生成目录,生成 ruby 代码

因为开篇咱们就用 Go 举了例子,上面再用 Java 举个例子吧:

protoc --java_out=. hello.proto

在当前目录导出 java 版本的代码,编译 hello.proto 音讯,执行成果如下:

下载再带小伙伴们理解一下 ProtoBuf 的进阶知识点吧:枚举类型、音讯嵌套和 Map 类型。

1.7 枚举类型

写 Java 的同学枚举肯定用的很溜,然而写 Go 的同学可能有点懵了,Go 是不间接反对枚举的,并没有 Enum 关键字。

关注我,后续会详解 Go 枚举相干的知识点,在这篇文章中不做重点介绍。

应用枚举的场景是这样的:

当定义一个音讯类型的时候,可能想为一个字段指定“预约义值”中的其中一个值,这时候咱们就能够通过枚举实现,比方这种:

syntax = "proto3";// 指定版本信息,非正文的第一行

enum SexType // 枚举音讯类型,应用 enum 关键词定义, 一个性别类型的枚举类型
{
    UNKONW = 0; //proto3 版本中,首成员必须为 0,成员不应有雷同的值
    MALE = 1;  // 1 男
    FEMALE = 2; // 2 女  0 未知
}

// 定义一个用户音讯
message UserInfo
{
    string name = 1; // 姓名字段
    SexType sex = 2; // 性别字段,应用 SexType 枚举类型
}

运行成果如下:

在理论开发中,咱们须要定义很多的 proto,咱们如何做到音讯的复用呢?

答案就是:“音讯嵌套”

1.8 音讯嵌套

咱们在开发 Java 和 PHP 时,常常嵌套应用类,也能够应用其余类作为本人的成员属性类型;在开发 Go 时常常嵌套应用构造体。

在 ProtoBuf 中同样反对音讯嵌套,能够在一个音讯中嵌套另外一个音讯,字段类型能够是另外一个音讯类型。

咱们来看上面 3 个经典示例:

1.8.1 援用其余音讯类型的用法

// 定义 Article 音讯
message Article {
  string url = 1;
  string title = 2;
  repeated string tags = 3; // 字符串数组类型
}

// 定义 ListArticle 音讯
message ListArticle {
  // 援用下面定义的 Article 音讯类型,作为 results 字段的类型
  repeated Article articles = 1; // repeated 关键词标记,阐明 articles 字段是一个数组
}

1.8.2 音讯嵌套

相似类嵌套一样,音讯也能够嵌套,比方这样:

message ListArticle {
  // 嵌套音讯定义
  message Article {
    string url = 1;
    string title = 2;
    repeated string tags = 3;
  }
  // 援用嵌套的音讯定义
  repeated Article articles = 1;
}

1.8.3 import 导入其余 proto 文件定义的音讯

咱们在理论开发中,通常要定义很多音讯,如果都写在一个 proto 文件,是不不便保护的。

小技巧:将音讯定义写在不同的 proto 文件中,在须要的时候能够通过 import 导入其余 proto 文件定义的音讯。

例子:

创立文件: article.proto

syntax = "proto3";

package nesting;

option go_package = "./;article";

message Article {
  string          url   = 1;
  string          title = 2;
  repeated string tags  = 3; // 字符串数组类型
}

创立文件: list_article.proto

syntax = "proto3";
// 导入 Article 音讯定义
import "article.proto";

package nesting;

option go_package = "./;article";

// 定义 ListArticle 音讯
message ListArticle {
  // 应用导入的 Result 音讯
  repeated Article articles = 1;
}

执行成果如下,咱们顺利生成了.pb.go 文件:

1.9 map 类型

咱们在 Go 语言开发中,最罕用的就是切片类型和 map 类型了。

切片类型在 ProtoBuf 中对应的就是 repeated 类型,后面咱们曾经介绍过了。

再重点介绍一下 map 类型,ProtoBuf 也是反对 map 类型的:

1.9.1 map 语法

map<key_type, value_type> map_field = N;

语法非常简单和通用,然而有几个问题须要咱们留神:

  1. key_type能够是任何整数或字符串类型(除浮点类型和字节之外的任何标量类型)。
  2. 留神:枚举不是无效的key_type
  3. value_type 能够是除另一个映射之外的任何类型。
  4. Map 字段不能应用 repeated 关键字润饰。

1.9.2 map 的例子

咱们举个典型的例子:学生的学科和分数就适宜用 map 定义:

syntax = "proto3";

package map;

option go_package = "./;score";

message Student{
  int64              id    = 1; //id
  string             name  = 2; // 学生姓名
  map<string, int32> score = 3;  // 学科 分数的 map
}

运行成果如下:

再强调一下

留神:Map 字段是不能应用 repeated 关键字润饰。

至此咱们曾经把握了 ProtoBuf 的所有知识点,是不是非常简单清晰呢?

上面咱们在 Go 我的项目中实战利用一下 ProtoBuf,从 ProtoBuf 中读取数据,并且转换为咱们罕用的构造体

5 分钟实战

1. 首先咱们定义 proto 文件

我创立了一个 demo 目录,创立了名为 study_info.proto 的文件

syntax = "proto3";

package demo;

option go_package = "./;study";

message StudyInfo {
  int64              id       = 1; //id
  string             name     = 2; // 学习的科目名称
  int32              duration = 3; // 学习的时长 单位秒
  map<string, int32> score    = 4; // 学习的分数
}

2. 生成代码

应用命令生成 pb.go 文件:

protoc --go_out=. study_info.proto 

3. 编写 go 文件

编写 go 文件,读取 ProtoBuf 中定义的字段,进行赋值,取值,转成构造体等操作:

proto 编码和解码的操作和 json 是十分像的,都应用“Marshal”和“Unmarshal”关键字。

package main

import (
   "fmt"
   "google.golang.org/ProtoBuf/proto"
   study "juejin/ProtoBuf/proto/demo"
)

func main() {
   // 初始化 proto 中的音讯
   studyInfo := &study.StudyInfo{}

   // 惯例赋值
   studyInfo.Id = 1
   studyInfo.Name = "学习 ProtoBuf"
   studyInfo.Duration = 180

   // 在 go 中申明实例化 map 赋值给 ProtoBuf 音讯中定义的 map
   score := make(map[string]int32)
   score["实战"] = 100
   studyInfo.Score = score

   // 用字符串的形式:打印 ProtoBuf 音讯
   fmt.Printf("字符串输入后果:%v\n", studyInfo.String())

   // 转成二进制文件
   marshal, err := proto.Marshal(studyInfo)
   if err != nil {return}
   fmt.Printf("Marshal 转成二进制后果:%v\n", marshal)

   // 将二进制文件转成构造体
   newStudyInfo := study.StudyInfo{}
   err = proto.Unmarshal(marshal, &newStudyInfo)
   if err != nil {return}
   fmt.Printf("二进制转成构造体的后果:%v\n", &newStudyInfo)
}

运行后果如下:

本文总结

ProtoBuf 作为开发微服务必选的数据交换协定,基于二进制传输,比 json/xml 更小,速度更快,应用也十分的简略。

通过这篇文章,咱们不仅学会了 ProtoBuf 的入门操作,还应用 Go 语言基于 ProtoBuf 编码解码了数据,进行了实战。

进阶局部带大家理解了 ProtoBuf 如何定义音讯、ProtoBuf 和 Go 数据类型的映射、枚举类型如何应用、通过音讯嵌套复用代码、应用 map 类型时须要留神的问题和小技巧。

微服务架构成为企业我的项目的必然选择已是趋势,如果你只会开发单体我的项目,请关注我,带你一起玩转微服务。

对于更文

近期会更新一系列 Go 实战进阶的文章,欢送大家关注我,这是近期会更新文章的常识脉络图,感兴趣的小伙伴能够关注一波,欢送日常催更。

天下难事,必作于易。想都是问题,做才有答案。站着不动,永远是观众。

小伙伴们还想看哪些内容,欢送在评论区留言。

一起学习,降级打怪

公众号:程序员降级打怪之旅

微信号:wangzhongyang1993

B 站视频:王中阳 Go

本文参加了思否技术征文,欢送正在浏览的你也退出。

退出移动版