在网络通信和通用数据交换等应用场景中经常使用的技术是JSON和XML,在微服务架构中则一般使用另外一个数据交换的协议的工具==ProtoBuf==
全称:protocol buffers,即协议缓冲区,是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据
和json/xml最大区别:json/xml基于文本格式,ProtoBuf是二进制格式
首先先去下载protoc
之后将解压后的bin目录放到环境变量中,在命令行执行protoc,若有输出则配置成功
这时候在go中使用,先在goland里写一段protoc代码
protobufsyntax = "proto3"; package hello; option go_package = "./;hello"; message Say{ int64 id = 1; string hello = 2; repeated string word = 3; }
在文件的目录下执行
propertiesprotoc --go_out=. hello.proto
这时候可能会报错
go'protoc-gen-go' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 --go_out: protoc-gen-go: Plugin failed with status code 1.
这时候是因为缺少了差距,下载即可
gogo get -u google.golang.org/protobuf/cmd/protoc-gen-go go install google.golang.org/protobuf/cmd/protoc-gen-go go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
代码解析:
protobuf// 定义proto版本号 syntax = "proto3"; // 定义包名,用于proto package hello; // 定义go包名,用于生成的.pd.go文件 option go_package = "./;hello"; // 定义消息体 message Say{ int64 id = 1; string hello = 2; repeated string word = 3; }
定义数组类型时,是在字段前面增加repeated关键词来实现,标记当前字段是一个数组
message类似go中定义的结构体,在protobuf中指的就是我们要定义的数据结构
==string query = 1==,其中的1就是分配标识号,在消息定义中,每个字段后面都有一个唯一的数字,这个就是标识号。
标识号的作用:在消息的二进制格式中识别各个字段,一旦开始使用就不能够再改变
注:分配标识号在每个消息内唯一,不同的消息体是可以拥有相同的标识号的。
小技巧:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。
要为将来有可能添加的、频繁出现的字段预留一些标识号
protobufmessage Reserver { // 保留2,5,7到10这些标识号 reserver 2,5,7 to 10; }
如果使用了这些保留的标识号,protocol buffer编译器无法编译通过,会输出警告信息。
编译器命令格式:
protobufprotoc [OPTION] PROTO_FILES
常用的OPTION选项
go--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 代码
Enum:当定义一个消息类型时,如果想为一个字段指定的”预定义值“中的其中一个值,这时候就可以通过枚举实现
protobufsyntax = "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,消息嵌套则可以帮助我们做到消息的服用
例如在go中时常嵌套使用结构体,在protobuf中同样可以如此,在一个消息中嵌套另外一个消息,字段类型可以是另外一个消息类型
而且在protoc文件中,可以import另外一个文件,且可以使用其定义的消息体
gomap<key_type,value_type> map_field = N;
RPC--远程过程调用,对于全称为:Remote Procedure Call,可以简单理解为一个节点请求另外一个节点提供的服务
RPC主要依赖于客户端与服务的建立socket链接,而http rest实现通讯的代价比较高,这是RPC的一个优势体现、
如果要将本地对象student的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定
如果addAge()方法在服务端,执行函数的函数体在远程机器上,又该如何调用?
网络传输层把调用的ID和序列化后的参数传给服务端,然后把计算出来的结果序列号传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
go// Client端 // Student student = Call(ServerAddr, addAge, student) 1. 将这个调用映射为Call ID。 2. 将Call ID,student(params)序列化,以二进制形式打包 3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层 4. 等待服务器返回结果 5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新 // Server端 1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap 2. 等待客户端请求 3. 得到一个请求后,将其数据包反序列化,得到Call ID 4. 通过在callIdMap中查找,得到相应的函数指针 5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果 6. 将student结果序列化后通过网络返回给Client
因为无法在同一个进程内,或者无法在同一个服务器上通过本地调用的方式实现需求,http能满足需求但是不够高效。
使用情况:比如不同系统间通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用
是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul是分布式的,高可用的,可横向扩展的
服务发现
客户端可以注册服务,程序可以轻松找到它们所依赖的服务
运行状况检查
Consul客户端可以提供任意数量的运行状况检查
KV存储
应用程序可以将Consul的层级键/值存储用于任何目的,包括动态配置,功能标记,协调,领导者选举等
安全服务通信
Consul可以为服务生成和分发TLS证书,建立相互的TLS连接
多数据中心
Consul支持多个数据中心
本文作者:Malyue
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!