Protocol Buffers设计要点

概述

一种开源跨平台的序列化结构化数据的协议。可用于存储数据或在网络上进行数据通信。它提供了用于描述数据结构的接口描述语言(IDL),也提供了根据 IDL 产生代码的程序工具。Protocol Buffers的设计目标是简单和性能,所以与 XML 相比更小且更快。在 Google,它被当作一个 RPC 系统的基础。

设计过程

Tag 表示

对于一条 person 信息,使用 JSON 可表示为:{ “age”: 30, “name”: “zhangsan”, “height”: 175.33, “weight”: 140 }。显然,中间有很多冗余的字符,比如 {," 等,为把数据变小一点,我们直接将其表示为:30|zhangsan|175.33|140,即通过直接将 value 使用分隔符 | 拼在一起,舍去了不必要的冗余字符(每条记录都要包含各个字段的字段名很浪费存储空间),这样大幅度地压缩了空间。然后接收端按照如下顺序解析 key-value 之间的关系。

字段1:age字段2:name字段3:height字段4:weight
30zhangsan175.33140

假设 height 这个字段为 null,我们其实是不必要传递这个字段的,这个时候我们需要传递的数据就为:30|zhangsan|140。但是在接收端,解析数据并按照顺序进行字段匹配的时候就会出问题。

字段1:age字段2:name字段3:height字段4:weight
30zhangsan140

显然已经乱套了,为了保证能够正确的配对,我们可以使用 tag 技术,即每个字段我们都用tag|value 的方式来存储的,在 tag 当中记录两种信息:

  • value 对应的字段的编号
  • value 的数据类型

因为 tag 中有字段编号信息,所以即使没有传递 height 字段的 value 值,根据编号也能正确的配对。

使用 tag 也会增加额外的空间,这跟 JSON 的 key/value 有什么区别吗? JSON 中的 key 是字符串,每个字符就会占据一个字节,像 name 这个 key 就会占据 4 个字节,但在 PB 中,tag 使用二进制进行存储,一般只会占据 1 个字节,它的代码为:

static int makeTag(final int fieldNumber, final int wireType) {return (fieldNumber << 3) | wireType;
}
  • fieldNumber 就是字段编号,比如 fieldNumber 为 1 表示age,为 2 表示 name 等;
  • wireType 表示字段的数据类型,以此来计算 value 占用字节的大小。

注:在 protobuf 当中,wireType可以支持的字段类型如下:
在这里插入图片描述

编码优化

我们知道整数在计算机当中占 4 个字节,但绝大部分的整数,比如价格,库存等,都是比较小的整数,实际用不了 4 个字节,比如 127 在计算机中的二进制为:

00000000 00000000 00000000 01111111

完全可以用最后 1 个字节来进行存储,protobuf 当中定义了 Varint 这种数据类型,可以用不同的长度来存储整数,进一步压缩数据的存储空间。

另外在计算机中,负数使用补码表示,比如 -1,它的二进制表示方式为:

11111111 11111111 11111111 11111111

显然就无法用 1 个字节来表示了,这个时候就可以使用 zigzag 算法对负数进行变换,最终可用 2 个字节来表示 -1。

Varints

《深入理解 Kafka:核心设计与实践原理》

Varints 是使用一个或多个字节来序列化整数的一种方法。数值越小,其占用的字节数就越少。Varints 中的每个字节都有一个位于最高位的 msb 位(most significant bit),除最后一个字节外,其余 msb 位都设置为 1,最后一个字节的 msb 位为 0。这个 msb 位表示其后的字节是否和当前字节一起来表示同一个整数。 除 msb 位外,剩余的 7 位用于存储数据本身,这种表示型又称为 Base 128。通常而言,一个字节 8 位可以表示 256 个值,所以称为 Base 256,而这里只能用 7 位表示,2 的 7 次方即 128。Varints 中采用的是小端字节序,即最小的字节放在最前面。

举个例子,比如数字 1,它只占一个字节,所以 msb 位为 0:

0000 0001

再举一个复杂点得例子,比如数字 300:

1010 1100 0000 0010

300 得二进制表示原本为 0000 0001 0010 1100 = 256 + 32 +8 + 4 = 300。那么为什么 300 的变长表示为上面这种形式呢?

首先去掉每个字节的 msb 位,表示为:

1010 1100 0000 0010 -> 010 1100 000 0010(varints 表示去掉 msb 的结果)

如前所述,varints 使用小端字节序的布局方式,所以这里两个字节的位置需要翻转再补上最高位:

010 1100 000 0010
-> 000 0010 010 1100(翻转)
-> 0000 0010 0010 1100(补上每字节的最高位 0)

Varints 可以用来表示 int32、int64、uint32、uint64、sint32、sint64、bool、enum 等类型。在实际使用过程中,如果当前字段可以表示为负数,那么对 int32/int64 和 sint32/sint64 而言,它们在进行编码时存在较大的区别。比如使用 int64 表示一个负数,那么哪怕是 -1(-1 正常需要 8 * 8 = 64 位来表示,而 varint 表示法,每个字节的最高位需要作为 msb,相当于每个字节只有 7 位,所以至少需要 10 字节(10 * 7 > 64 > 9 * 7)来表示),其编码后的长度始终为 10 个字节,就如同对待一个很大的无符号长整型数一样。为了使编码更加高效,Varints 使用了 ZigZag 的编码方式。

ZigZag 编码以一种锯齿形(zig-zags)的方式来回穿梭正负整数,将带符号整数映射为无符号整数,这样可以使绝对值较小的负数仍然享有较小的 Varints 编码值,比如 -1 编码为 1,1 编码为 2,-2 编码为 3,如表5-1所示。
ZigZag编码
对应的公式为:

(n << 1) ^ (n >> 31) (sin32)、(n << 1) ^ (n >> 63)(sin64)

以 -1 为例, 其二进制表现形式为 1111 1111 1111 1111 1111 1111 1111 1111 ( 补码 )。

( n << 1 ) = 1111 1111 1111 1111 1111 1111 1111 1110
(n >> 31) = 1111 1111 1111 1111 1111 1111 1111 1111
(n << 1) ^ (n >> 31 ) = 1

最终 -1 的 Varints 编码为 0000 0001 ,这样原本用 4 个字节表示的 -1 现在可以用 1 个字节来表示了。

前面说过 Varints 中的一个字节中只有 7 位是有效数值位, 即 只能表示 128 个数值 ,转变成绝对值之后其实质上只能表示 64 个数值 。 比如对消息体长度而言,其值肯定是大于等于 0 的正整数,那么一个字节长度的 Varints 最大只能表示 64 。 65 的二进制数表示为:

0100 0001

经过 ZigZag 处理后为:

1000 00 1 0 A 0000 0000 = 1000 0010

每个字节的低 7 位是有效数值位,所以 1000 0010 进一步转变为:

000 0001 000 0010

而 Varints 使用小端字节序,所以需要翻转一下位置:

000 0010 000 0001

设置非最后一个字节的 msb 位为 1 ,最后一个字节的 msb 位为 0,最终有:

1000 0010 0000 0001

所以最终 65 表示为 1000 0010 0000 0001 ,而 64 却表示为 0 100 0000 。

解码

因为每个字段都用 tag|value 来表示,在 tag 中包含了 value 的数据类型信息,所以可以直接从 tag 中得知 value 的字节大小,从而快速地解析出数据。例如 value 是 bool 型,我们就知道其占了 1 个字节,程序从 tag 后面直接读一个字节就可以解析出 value,而 JSON 则需要进行字符串解析才可以办到。

如果 value 是字符串类型的,我们无法从 tag 当中得知 value 的实际长度,就不得不做字符串匹配操作,要知道字符串匹配是非常耗时的。 为了能够快速解析字符串类型的数据,protobuf 在存储的时候,对其做了特殊的处理,即其存储被分做三部分:tag|length|value,其中的 leg 记录了字符串的长度,同样使用了 varint 来存储,然后程序从 leg 后截取 leg 个字节的数据作为 value。

在这里插入图片描述

协议比较

Avro、ProtoBuf、Thrift 的模式演进之法【翻译】

如果有一些数据,想存储在文件中,或者通过网络发送出去,对数据的处理主要分为如下几个阶段:

  1. 使用编程语言内置的序列化机制,如 Java Serialization、 Ruby Marshal 或 Python pickle,或者甚至发明自己的格式。
  2. 意识到被锁定在一种编程语言中是很糟糕的,所以转向使用一种广泛支持的、与语言无关的格式,比如 JSON (或者 XML)。
  3. 觉得 JSON 过于冗长,解析速度太慢。也会恼火它竟然无法区分整数和浮点。所以出现了一种类似于 JSON 的格式,但它是二进制的。(比如: MessagePack、 BSON、Binary JSON 等)。
  4. 当使用不一致的协议类型进行通信时,总会出现对象字段匹配失效的问题。此外,类似于 JSON 的二进制文件并没有那么紧凑,因为它们仍会一遍又一遍地存储字段名。如果有一个协议,可以避免存储对象的字段名,则可以节省更多字节。可用选项通常是 Thrift, Protocol Buffers(以下简称 ProtoBuf)或 Avro。这三种序列化协议都是基于模式设计的,为 Java 开发人员提供了高效的、跨语言数据序列化和代码生成能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/772460.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(执行上下文作用域链)前端八股文修炼Day4

一 作用域作用域链 作用域&#xff08;Scope&#xff09;是指程序中定义变量的区域&#xff0c;作用域规定了在这个区域内变量的可访问性。在 JavaScript 中&#xff0c;作用域可以分为全局作用域和局部作用域。 全局作用域&#xff1a;在代码中任何地方都可以访问的作用域&am…

基于Springboot的狱内罪犯危险性评估系统的设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的狱内罪犯危险性评估系统的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#…

宝塔部署项目

如何在云服务器上使用宝塔 登录到你的云服务器后&#xff0c;执行宝塔面板安装命令&#xff0c;阿里云服务器网使用的CentOS操作系统&#xff0c;命令如下 yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh …

题。。。。

O - 胜利大逃亡(续) 题目分析 bfs状态压缩&#xff08;在bfs的基础上&#xff0c;存储持有不同钥匙时&#xff0c;此点位是否走过的情况&#xff09;&#xff1b; -----状态压缩使用二进制实现&#xff0c;同时通过位运算修改是否转移至另一状态&#xff08;详情见代码及注释…

解决 Xshell 等工具连接虚拟机失败

这里以 Xshell 等工具连接 Linux 虚拟机为例 对于我们使用 Xshell 等工具连接虚拟机失败&#xff0c;我们可以从以下的几个方面进行检查和解决 检查连接工具中的连接会话配置是否正确 对于这方面&#xff0c;我们要检查连接工具中连接会话配置的虚拟机 IP 地址和端口号是否正…

Postman核心功能解析-参数化和测试报告

一、参数化处理 参数化&#xff1a;针对于某一个接口&#xff0c;有大量的的测试数据需要批量验证&#xff0c;一个一个的更改请求参数太耗时耗力&#xff0c;使用参数化批量处理数据会比较高效&#xff0c;常规通过文档参数化实现。 创建文件 格式CSV 文件内第一行信息 需要…

操作系统的理解|冯·若依曼体系结构|进程的状态

操作系统的理解 冯诺伊曼体系结构为什么必须通过内存然后到cpu存储金字塔冯诺伊曼结构的改进在哪&#xff1f;我们可不可以全部用寄存器来做存储器在硬件数据流动角度学以致用&#xff1a;解释程序运行为什么要加载到内存程序没被运行之前存在哪里&#xff1f; 操作系统概念广义…

应急响应实战笔记04Windows实战篇(2)

第2篇&#xff1a;蠕虫病毒 0x00 前言 ​ 蠕虫病毒是一种十分古老的计算机病毒&#xff0c;它是一种自包含的程序&#xff08;或是一套程序&#xff09;&#xff0c;通常通过网络途径传播&#xff0c;每入侵到一台新的计算机&#xff0c;它就在这台计算机上复制自己&#xff…

第一个C++程序,我也没看明白,暂时。

#include<iostream> using namespace std; int main() { cout << "hello world and you too number!" << endl; system("pause"); return 0; } 运行结果为&#xff1a;

优化生产流程,解决无尘布擦拭留下划痕问题

在现代化工生产中&#xff0c;无尘布被广泛应用于清洁工作&#xff0c;然而&#xff0c;河北一家化工企业在使用无尘布进行擦拭时却发现产品表面留下了划痕&#xff0c;给生产过程带来了不小的困扰。针对这一问题&#xff0c;一家化工企业向供应商优斯特寻求解决方案&#xff0…

AI视频激光综合驱鸟装置:全自动、大范围驱鸟 | 真驱鸟科技

在电力系统中&#xff0c;鸟害事故已成为一个不容忽视的问题&#xff0c;直接威胁到电网的正常运行。但鸟类拥有极强的环境适应能力&#xff0c;它们能够在各种环境中生存和繁衍。这种强大的适应性使得传统的单一功能驱鸟器&#xff0c;在面对鸟类时显得力不从心&#xff0c;无…

苹果与百度合作,将在iPhone 16中使用生成式AI

3月25日&#xff0c;《科创板日报》消息&#xff0c;苹果将与百度进行技术合作&#xff0c;为今年即将发布的iPhone16、Mac系统和iOS 18提供生成式AI&#xff08;AIGC&#xff09;功能。 据悉&#xff0c;苹果曾与阿里巴巴以及另外一家国产大模型厂商进行了技术合作洽谈。最终…

#Linux系统编程(ps和kill命令)

&#xff08;一&#xff09;发行版&#xff1a;Ubuntu16.04.7 &#xff08;二&#xff09;记录&#xff1a; &#xff08;1&#xff09;ps命令 可以列出系统中当前运行的那些进程。 命令格式&#xff1a;ps 参数(常用-aux) 命令功能&#xff1a;用来显示当前进程的状态 常…

[STL]priority_queue类及反向迭代器的模拟实现

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 今日主菜&#xff1a; priority_queue类及反向迭代器 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 向着c&…

istio 设置 istio-proxy sidecar 的 resource 的 limit 和 request

方式一 修改 configmap 查看当前 sidecar 的 cpu 和 memory 的配额 在 istio-sidecar-injector 中查找,修改后重启 pod 可以生效(下面那个 proxy_init 配置不管,不知道是干嘛的) 方式二 如果是通过 iop 安装的 istio,可以修改 iop 文件中的配置 spec:values:global:…

程序员35岁真的就是危机吗?

前言 35岁被认为是程序员职业生涯的分水岭&#xff0c;许多程序员开始担忧自己的职业发展是否会受到年龄的限制。有人担心随着年龄的增长&#xff0c;技术更新换代的速度会使得资深程序员难以跟上&#xff1b;而另一些人则认为&#xff0c;丰富的经验和深厚的技术积累是年轻程…

LeetCode 309—— 买卖股票的最佳时机含冷冻期

阅读目录 1. 题目2.解题思路3. 代码实现 1. 题目 2.解题思路 根据题意&#xff0c;每一天有这样几个状态&#xff1a;买入股票、卖出股票、冷冻期、持有股票&#xff0c;因此&#xff0c;我们假设 f 为每天这几个状态下对应的最大收益&#xff0c;由于持有股票时不知道是哪天买…

RocketMQ学习笔记:消息存储模型,持久化文件,过期文件删除

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、消息存储结构1.1、CommitLog详解1.1.1、CommitLog存储的优点 1.2、ConsumeQueue详解1.3、Index详解 2、持久化文件3、过期文件删除机制3.1、判断过期文件3.2、删除的时机 1、消息存储结构…

Navicat15安装教程

直接开始Navicat15的安装教程 下载好上面的资源&#xff0c;解压后得到以下文件 1. 安装 Navicat ①双击 navicat150_premium_cs_x64.exe&#xff0c;准备安装 Navicat 15 ②无脑一直下一步就行&#xff0c;到下图画面就安装成功了。 2.安装完成以后&#xff0c;先不要启动…

第三十一天-Flask-ORM-sqlalchemy

目录 1.什么是ORM 2.flask-sqlalchemy 1安装 2.配置 3.数据库模型设计 ​编辑 4.插入修改删除 5.查询 1.什么是ORM 2.flask-sqlalchemy 1安装 2.配置 3.数据库模型设计 4.插入修改删除 5.查询