Protocol Buffer (PB)协议阐述与实践

写在开头

哈喽,各位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 的编号。(有点像是编写序号😂)

字段类型:

类型 说明
int32int64 用于表示整数,分别是 32 位和 64 位有符号整数。例如:int32 count = 1;定义了一个名为count的 32 位整数字段,其中1是字段编号。
uint32uint64 用于表示整数,分别是 32 位和 64 位有符号整数。例如:int32 count = 1;定义了一个名为count的 32 位整数字段,其中1是字段编号。
floatdouble 用于表示单精度和双精度浮点数。
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 服务),则会对收到的序列化数据进行反序列化操作。这样就完成了数据从一个服务到另一个服务的传递。


至此,本篇文章就写完啦,撒花撒花。

相关推荐
迷雾漫步者22 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
工业甲酰苯胺2 小时前
分布式系统架构:服务容错
数据库·架构
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
Java程序之猿4 小时前
微服务分布式(一、项目初始化)
分布式·微服务·架构