关于思否技术征文: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

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理