背景

一个简单的代理程序,发现单核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的底层库直接解码二进制数据的更多相关文章

  1. 【笔记】直接使用protocol buffers的底层库,对特定场景的PB编解码进行处理,编码性能提升2.4倍,解码性能提升4.8倍

    接上一篇文章:[笔记]golang中使用protocol buffers的底层库直接解码二进制数据 最近计划优化prometheus的remote write协议,因为业务需要,实现了一个remote ...

  2. 【新手笔记】golang中使用protocol buffers 3

    主要参考了这篇帖子:https://segmentfault.com/a/1190000009277748 1.下载windows版本的PB https://github.com/protocolbu ...

  3. 如何在 PHP 中处理 Protocol Buffers 数据

    Protocol Buffers是谷歌定义的一种跨语言.跨平台.可扩展的数据传输及存储的协议,因为将字段协议分别放在传输两端,传输数据中只包含数据本身,不需要包含字段说明,所以传输数据量小,解析效率高 ...

  4. 在Android中使用Protocol Buffers(上篇)

    本文来自网易云社区. 总览 先来看一下 FlatBuffers 项目已经为我们提供了什么,而我们在将 FlatBuffers 用到我们的项目中时又需要做什么的整体流程.如下图: 在使用 FlatBuf ...

  5. 在Android中使用Protocol Buffers(下篇)

    本文来自网易云社区. FlatBuffers编码数组 编码数组的过程如下: 先执行 startVector(),这个方法会记录数组的长度,处理元素的对齐,准备足够的空间,并设置nested,用于指示记 ...

  6. golang gin框架中使用protocol buffers和JSON两种协议

    首先,我使用protobuf作为IDL,然后提供HTTP POST + JSON BODY的方式来发送请求. 能不能使用HTTTP POST + PB序列化后的二进制BODY呢? 做了一下尝试,非常简 ...

  7. 在Android中使用Protocol Buffers(中篇)

    本文来自网易云社区. FlatBuffers 编码原理 FlatBuffers的Java库只提供了如下的4个类: ./com/google/flatbuffers/Constants.java ./c ...

  8. Protocol Buffers学习笔记

    Protocol Buffers学习笔记 1. 简介 Protocol Buffers是google发明的一种数据交换格式,独立于语言,独立于平台.与其他的数据交换格式有所不同,Protocol Bu ...

  9. protobuf Protocol Buffers 简介 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

随机推荐

  1. cmake配置项目引用动态库

    note 本文将介绍使用FIND_PACKAGE配置项目动态库的方法 cmake version: 3.18 platform: win10 20H2 概述 创建了一个动态库,再由主项目调用该动态库. ...

  2. 【LeetCode】面试题 01.07. 旋转矩阵

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 两次翻转 日期 题目地址:https://leetco ...

  3. 【LeetCode】637. Average of Levels in Binary Tree 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:DFS 方法二:BFS 日期 题目地址:ht ...

  4. 【LeetCode】241. Different Ways to Add Parentheses 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:递归构建所有表达式 方法二:分而治之 日期 ...

  5. 【LeetCode】116. 填充每个节点的下一个右侧节点指针 Populating Next Right Pointers in Each Node 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...

  6. hdu 1430 (BFS 康托展开 或 map )

    第一眼看到这题就直接BFS爆搜,第一发爆了内存,傻逼了忘标记了,然后就改,咋标记呢. 然后想到用map函数,就8!个不同的排列,换成字符串用map标记.然后又交一发果断超时,伤心,最恨超时,还不如来个 ...

  7. 标准基座获取定位可以获取address城市,自定义基座获取不到address

    正常的返回应该 { "type": "WGS84", "altitude": 0, "latitude": 31.830 ...

  8. vue源码中computed和watch的解读

    computed 会基于其内部的 响应式依赖 进行缓存. 只在相关 响应式依赖发生改变 时 它们才会重新求值. 可以在将模板中使用的常量放在计算属性中. watch 监听数据变化,并在监听回调函数中返 ...

  9. 在页面中添加两个 <select> 标签,用来显示年份和月份;同时添加两个 <ul> 标签,一个用来显示星期,另一个用来显示日期 在 JavaScript 脚本中动态添加年份和月份,获取当前日期的年份

    查看本章节 查看作业目录 需求说明: 使用 JavaScript 中的 Date 对象,在页面上显示一个万年历.选择不同的年份和月份,在页面中显示当前月的日历 实现思路: 在页面中添加两个 <s ...

  10. MySQL数据库基础(4)SELECT 数据查询

    目录 一.SELECT 选择列表 二.MySQL 运算符 三.定制显示查询结果 四.模糊查询 一.SELECT 选择列表 1.语法 SELECT <COLUMN1, COLUMN2, COLUM ...