【笔记】golang中使用protocol buffers的底层库直接解码二进制数据
背景
一个简单的代理程序,发现单核QPS达到2万/s左右就上不去了,40%的CPU消耗在pb的decode/encode上面。
于是我想,对于特定的场景,直接从[]byte中取出字段,而不用完全的把整个结构在内存展开,岂不是要快很多。
so, 温习了一些PB二进制格式的知识。
pb的二进制格式:
参考的文章有:
几个关键点总结如下:
- 5 bit的 field index
- 3 bit的wire type
- wire type的定义如下:google.golang.org/protobuf/encoding/protowire/wire.go
const (
VarintType Type = 0 //int , float等全在这里
Fixed32Type Type = 5
Fixed64Type Type = 1
BytesType Type = 2 //字符串,或者嵌套的子类型
StartGroupType Type = 3 //废弃
EndGroupType Type = 4 //废弃
// Map 类型呢 ?
)
- 如果wire type 是 2, 则后续紧接着是长度信息
- bit 0 开头,说明用一个字节表示长度
- bit 10开头,说明2个字节表示长度
- bit 110开头,说明3个字节表示长度
- 以此类推……
- 如果wire type是 1或5,则很简单,后续的4字节或8字节是值
- 这个值被理解成int / uint / float等,就要看元数据的定义了
- 如果wire type 是 0,这里非常复杂
- 如果以 bit 0开头,只有 7 bit 表示值
- 如果以bit 10开头,后续的 14 bit 表示值
- 如果以bit 110开头,后续的 21 bit表示值
- 以此类推
- 值的内容以 Zigzag 编码 来表示
- 注意:二进制格式中唯一的元数据就是field index,除此之外不包含任何元数据信息。需要靠额外的元数据信息来指导如何decode这些二进制数据。
实操
PB二进制生成的代码:
import (
"github.com/golang/protobuf/proto"
"github.com/prometheus/prometheus/prompb"
"google.golang.org/protobuf/encoding/protowire"
)
func Test_make_pb(t *testing.T){
wr := &prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "test_metric_1",
},
{
Name: "job",
Value: "test1",
},
},
Samples: []prompb.Sample{
{
Value: 123.456,
Timestamp: int64(time.Now().UnixNano()) / 1000000,
},
},
},
},
Metadata: nil,
}
t.Logf("%s", wr.String())
buf, _ := proto.Marshal(wr)
t.Logf("\n%s\nlen=%d",
stringutil.HexFormat(buf), len(buf))
}
pb对应的二进制数据为:
0a 3b 0a 19 0a 08 5f 5f 6e 61 6d 65 5f 5f 12 0d | ; __name__
74 65 73 74 5f 6d 65 74 72 69 63 5f 31 0a 0c 0a | test_metric_1
03 6a 6f 62 12 05 74 65 73 74 31 12 10 09 77 be | job test1 w
9f 1a 2f dd 5e 40 10 a7 c6 90 f9 bd 2f | / ^@ /
假设我以JSON来描述上面的结构:
{
"id" :1,
"wire_type":2,
"body_len" : 55,
"child":[
{
"id" :1,
"wire_type":2,
"idx": 0,
"body_len" : 25,
"child":[
{
"id" :1,
"wire_type":2,
"body_len" : 8,
"value": "__name__",
},
{
"id":2,
"wire_type":2,
"body_len" : 13,
"value": "test_metric_1",
}
],
},
{
"id" : 1, //这个理解为属于第一组。这个节点和上个节点的ID都是1,因此反推出这两个节点属于repeated类型
"body_len" : 12,
"idx": 1,
"child":[
{
"id":1,
"body_len" : 3,
"value":"job"
},
{
"id":2,
"body_len" : 5,
"value":"test1"
},
]
},
{
"id": 2,
"wire_type":2,
"idx": 2,
"body_len": 12,
"child":[
{
"id":1,
"wire_type": 1, //64bit, float64
"value":"\x77\xbe\x9f\x1a\x2f\xdd\x5e\x40", //123.456
},
{
"id":2,
"wire_type":0, //timestamp
"value": "\xa7\xc6\x90\xf9\xbd\x2f"
}
]
}
]
}
后续打算基于PB的底层库来实现更高效率更少内存(但是非常非常难用)的库!
【笔记】golang中使用protocol buffers的底层库直接解码二进制数据的更多相关文章
- 【笔记】直接使用protocol buffers的底层库,对特定场景的PB编解码进行处理,编码性能提升2.4倍,解码性能提升4.8倍
接上一篇文章:[笔记]golang中使用protocol buffers的底层库直接解码二进制数据 最近计划优化prometheus的remote write协议,因为业务需要,实现了一个remote ...
- 【新手笔记】golang中使用protocol buffers 3
主要参考了这篇帖子:https://segmentfault.com/a/1190000009277748 1.下载windows版本的PB https://github.com/protocolbu ...
- 如何在 PHP 中处理 Protocol Buffers 数据
Protocol Buffers是谷歌定义的一种跨语言.跨平台.可扩展的数据传输及存储的协议,因为将字段协议分别放在传输两端,传输数据中只包含数据本身,不需要包含字段说明,所以传输数据量小,解析效率高 ...
- 在Android中使用Protocol Buffers(上篇)
本文来自网易云社区. 总览 先来看一下 FlatBuffers 项目已经为我们提供了什么,而我们在将 FlatBuffers 用到我们的项目中时又需要做什么的整体流程.如下图: 在使用 FlatBuf ...
- 在Android中使用Protocol Buffers(下篇)
本文来自网易云社区. FlatBuffers编码数组 编码数组的过程如下: 先执行 startVector(),这个方法会记录数组的长度,处理元素的对齐,准备足够的空间,并设置nested,用于指示记 ...
- golang gin框架中使用protocol buffers和JSON两种协议
首先,我使用protobuf作为IDL,然后提供HTTP POST + JSON BODY的方式来发送请求. 能不能使用HTTTP POST + PB序列化后的二进制BODY呢? 做了一下尝试,非常简 ...
- 在Android中使用Protocol Buffers(中篇)
本文来自网易云社区. FlatBuffers 编码原理 FlatBuffers的Java库只提供了如下的4个类: ./com/google/flatbuffers/Constants.java ./c ...
- Protocol Buffers学习笔记
Protocol Buffers学习笔记 1. 简介 Protocol Buffers是google发明的一种数据交换格式,独立于语言,独立于平台.与其他的数据交换格式有所不同,Protocol Bu ...
- protobuf Protocol Buffers 简介 案例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
随机推荐
- cmake配置项目引用动态库
note 本文将介绍使用FIND_PACKAGE配置项目动态库的方法 cmake version: 3.18 platform: win10 20H2 概述 创建了一个动态库,再由主项目调用该动态库. ...
- 【LeetCode】面试题 01.07. 旋转矩阵
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 两次翻转 日期 题目地址:https://leetco ...
- 【LeetCode】637. Average of Levels in Binary Tree 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:DFS 方法二:BFS 日期 题目地址:ht ...
- 【LeetCode】241. Different Ways to Add Parentheses 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:递归构建所有表达式 方法二:分而治之 日期 ...
- 【LeetCode】116. 填充每个节点的下一个右侧节点指针 Populating Next Right Pointers in Each Node 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...
- hdu 1430 (BFS 康托展开 或 map )
第一眼看到这题就直接BFS爆搜,第一发爆了内存,傻逼了忘标记了,然后就改,咋标记呢. 然后想到用map函数,就8!个不同的排列,换成字符串用map标记.然后又交一发果断超时,伤心,最恨超时,还不如来个 ...
- 标准基座获取定位可以获取address城市,自定义基座获取不到address
正常的返回应该 { "type": "WGS84", "altitude": 0, "latitude": 31.830 ...
- vue源码中computed和watch的解读
computed 会基于其内部的 响应式依赖 进行缓存. 只在相关 响应式依赖发生改变 时 它们才会重新求值. 可以在将模板中使用的常量放在计算属性中. watch 监听数据变化,并在监听回调函数中返 ...
- 在页面中添加两个 <select> 标签,用来显示年份和月份;同时添加两个 <ul> 标签,一个用来显示星期,另一个用来显示日期 在 JavaScript 脚本中动态添加年份和月份,获取当前日期的年份
查看本章节 查看作业目录 需求说明: 使用 JavaScript 中的 Date 对象,在页面上显示一个万年历.选择不同的年份和月份,在页面中显示当前月的日历 实现思路: 在页面中添加两个 <s ...
- MySQL数据库基础(4)SELECT 数据查询
目录 一.SELECT 选择列表 二.MySQL 运算符 三.定制显示查询结果 四.模糊查询 一.SELECT 选择列表 1.语法 SELECT <COLUMN1, COLUMN2, COLUM ...