写在开头
哈喽,各位UU们好吖!😋
在当今的软件开发领域,数据的高效处理与传输是至关重要的环节。随着系统架构的日益复杂和分布式应用的广泛普及,我们需要一种可靠且高效的方式来序列化和传输结构化数据。
注意到关键字没有?本次咱们要来分享的所谓 PB 协议就是来做这个事情的,这点目标要先明确下来❗
官方文档:传送门
😁这次要分享的内容可能会比较枯燥一些,具体情况如下,请诸君按需食用哈。
什么是 PB 协议?
Protocol Buffers
是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
这说得啥呀?😅 没办法,官方标准解释。。。
来看看它的使用场景:
PB 协议具有语言无关、平台无关以及出色的序列化效率等显著优势,特别适用于大规模数据存储、分布式系统间的网络通信以及对性能要求较高的微服务架构等场景。
Em...说得很有道理,但是,不是很懂呀,没有更明确的场景需求❓
当然。
在日常的业务开发中,我们常常会面临处理海量数据以及频繁进行服务间交互的挑战,例如在构建一个电商平台时,商品信息、用户数据、订单详情等结构化数据需要在不同的服务(如商品服务、用户服务、订单服务)之间快速且准确地传输与处理。(也就是跨服务之间传递结构化数据嘛)
还有一种常见场景,用于IM应用的即时通讯服务中进行数据的传递,因为很多时候IM应用都是跨服务、跨平台、跨终端的。
PB 协议能够极大地优化这些数据的传输效率,减少数据体积,从而提升整个系统的性能与响应速度。
懂了没❓😋
还有, PB 是一种二进制的数据格式❗这点很关键。
二进制的情况就不用小编过多介绍了吧😗 ?与文本格式(如 JSON、XML)相比,二进制格式本身就具有更紧凑的特点,即更小、更快呗,主要在传输与解析方面体现。
基本语法
PB 协议的内容都是保存在以 .proto
后缀结尾的文件中的❗
咱们来假设需要定义一个 User
的数据,它的属性有 id/name/age/sex/...
等等吧。
那么,咱们第一步就要来创建一个 user.proto
的文件。
接下来,定义具体的数据内容情况可以分为三步:
- 版本声明
Protocol Buffers 3(目前最常用版本)的语法以syntax = "proto3";
开头。这声明了所使用的 PB 协议的版本,让编译器知道按照哪个版本的规则来处理后续的定义,一般写在开头。例如:
js
syntax = "proto3";
- 包(Package)声明
包声明用于防止不同消息类型之间的命名冲突,类似于编程语言中的命名空间。例如,咱们的 User
数据,我就可以定义成 user_data
,具体如下:
js
package user_data;
"消息类型" 其实就是一个直译,一个称谓,你可以认为是 "数据" 、 "模块" ? 或者可以继续往下看看。
- 消息(Message)定义
消息是 PB 协议的核心,它定义了一种结构化的数据类型。消息中的每个字段都有一个唯一的编号和类型。
字段编号,每个字段都有一个唯一的正整数编号,编号在消息定义内部必须是唯一的。编号用于在二进制序列化和反序列化过程中识别字段。编号范围是 1 - 2^29 - 1,其中 1 - 15 在编码时占用一个字节,16 - 2047 占用两个字节。所以,对于频繁出现的字段,建议使用 1 - 15 的编号。(有点像是编写序号😂)
字段类型:
类型 | 说明 |
---|---|
int32 、int64 |
用于表示整数,分别是 32 位和 64 位有符号整数。例如:int32 count = 1; 定义了一个名为count 的 32 位整数字段,其中1 是字段编号。 |
uint32 、uint64 |
用于表示整数,分别是 32 位和 64 位有符号整数。例如:int32 count = 1; 定义了一个名为count 的 32 位整数字段,其中1 是字段编号。 |
float 、double |
用于表示单精度和双精度浮点数。 |
bool |
表示布尔值,例如bool is_valid = 2; 。 |
string |
用于表示 UTF - 8 编码的字符串,如string name = 3; 。 |
bytes |
用于表示字节序列,可以用来存储二进制数据。 |
来瞧瞧咱们的 User
消息定义是如何的,user.proto
文件:
js
syntax = "proto3";
package user_data;
// 定义一个名为User的消息类型,用于表示用户相关的数据结构
message User {
// 定义用户的唯一标识,使用32位有符号整数类型
int32 id = 1;
// 定义用户的姓名,使用UTF-8编码的字符串类型
string name = 2;
// 定义用户的年龄,使用32位有符号整数类型
int32 age = 3;
// 定义用户的性别,假设只有男女采用布尔的形式
bool sex = 4;
}
是不是还是挺容易的?😋
当前,上面咱们说过 PB 是具备结构化,所以,消息定义也允许嵌套使用,如:
js
syntax = "proto3";
package user_data;
message User {
int32 id = 1;
string name = 2;
int32 age = 3;
bool sex = 4;
message InnerMessage {
string first_name = 1;
string last_name = 2;
}
InnerMessage fullname = 5;
}
这就有趣多了吧😁,不过,这里小编就不过多阐述了,更多的细节可以翻翻官方文档,这玩意其实和咱们写 JSON
是差不多的,就是语法的不同,个人感觉。😋
在Node服务中实践PB数据
上面,咱们就定义完了一个 User
的 "消息",它清晰地定义了具体的数据结构情况。这个定义是后续所有操作的基础,无论是在何种编程语言环境下,只要按照这个定义来,就能保证数据结构的一致性。
比如,一个用 Java 编写的服务端和一个用 Node 编写的客户端,只要它们都使用相同的 .proto
文件定义,就可以正确地理解和处理彼此发送的数据。
咱们以 Node 为例,来操作这个 user.proto
文件。
项目初始化:
js
npm init -y
先安装相关库:
js
npm install protobufjs
创建 server.js
文件:
js
const protobuf = require('protobufjs');
// 加载.proto文件
protobuf.load('user.proto', function(err, root) {
if (err) {
return console.error('加载.proto文件出错:', err);
}
// 根据.proto文件中的定义获取消息类型
const User = root.lookupType('user_data.User');
// 创建一个实际的User对象
let user = { id: 10086, name: '橙某人', age: 18, sex: true };
// 对对象进行序列化
let buffer = User.encode(user).finish();
console.log('序列化后的二进制数据:', buffer);
// 对二进制数据进行反序列化
let decoded = User.decode(buffer);
console.log('反序列化后的对象:', decoded);
});
执行 node server.js
命令:
通过这个简单的测试案例,相信你能了解到在 Node.js 环境下如何使用 PB 协议来序列化和反序列化数据结构。在实际应用中,可以根据具体的需求定义更复杂的 .proto
文件和处理更复杂的数据交互。
一般情况下,
.proto
文件本身可不会通过请求发送给 Java 或 Node 服务。.proto
文件主要是用于在开发阶段定义数据结构,就像一个蓝图。在服务实现阶段,开发人员会使用协议编译器(例如,对于 Java 有protoc - - java_out =...
,对于 Node.js 可以结合protobufjs
等工具)根据.proto
文件生成对应的代码。这些生成的代码会被编译进 Java 或 Node 服务中,作为数据处理的一部分。传输过程:
当一个服务(比如 Java 服务)需要向另一个服务(Node 服务)发送数据时,首先会将内部的数据对象(通常是基于
.proto
文件定义生成的对应语言的对象)进行序列化。在接收端(Node 服务),则会对收到的序列化数据进行反序列化操作。这样就完成了数据从一个服务到另一个服务的传递。
至此,本篇文章就写完啦,撒花撒花。