• 【笔记】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("
    %s
    len=%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":"x77xbex9fx1ax2fxddx5ex40",  //123.456
                    },
                    {
                        "id":2,
                        "wire_type":0,  //timestamp
                        "value": "xa7xc6x90xf9xbdx2f"
                    }
                ]
            }
        ]
    }
    

    后续打算基于PB的底层库来实现更高效率更少内存(但是非常非常难用)的库!

  • 相关阅读:
    【转】字典转模型需要注意的问题,以及第三方框架来处理字典转模型
    【转】使用SOAP访问Web服务
    Foundation框架2
    Foundation框架1
    什么是Protocol
    什么是Block
    什么么是Category
    ARC
    autorelease简介
    循环retain
  • 原文地址:https://www.cnblogs.com/ahfuzhang/p/15260582.html
Copyright © 2020-2023  润新知