1.Protocol Buffer编码
什么是Protocol Buffer(下称pb),这边也就不介绍了,通常要求不是特别严格的时候Json,甚至XML就能满足要求。
如需了解这边传送门:
message Test1 { required int32 a = 1; }
pb以二进制形式存储,以Base 128 Varints
进行编码,Test1的存储内容仅为3个字节:
08 96 01
这是什么意思呢?
Varints are a method of serializing integers using one or more bytes. Smaller numbers take a smaller number of bytes.
Each byte in a varint, except the last byte, has the most significant bit (msb) set – this indicates that there are further bytes to come. The lower 7 bits of each byte are used to store the two's complement representation of the number in groups of 7 bits, least significant group first.
在Varints中,每个Byte的取低7位进行存储实际内容。最高位用1来表示most significant bit
,既表示下一个Byte跟当前Byte存储的是同一个Varint;用0来表示least significant bit
。看完这里,相信大家知道,对于一个完全的int64,其实用varints真实物理存储会超过64位。
那么接下来解释Test1的存储,首先先一张表
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups (deprecated) |
4 | End group | groups (deprecated) |
5 | 32-bit | fixed32, sfixed32, float |
当一个Message被encode的时候,message的key其实只存了filed number以及一个wire type,filed number被你定义在.proto文件中,wire type即上面的表格中对应的。
每一个varint key 为 field_number << 3 | wire_type,言下之意,一个varint key的某三位存的是wire type,后4位存的是filed number。
以Test1为例,我们知道第一Byte通常就是varint key,Test1的内容是 08,也就是:
000 1000
于是我们有filed number = 0001,wire type = 0。因此我们也得到了如下信息,在filed number = 1的地方 存储着一个varint。
然后我们接下去读
96 01 = 1001 00110 0000 0001
基于varint的存储表示: 000 0001 001 00110 -> 10010110 = 150
所以我们知道存储的值为150。
2.Lua PB 与int_64
情景是这样的,我们有一个server用的是openresty,语言用的是lua,lua的解释器是luajit。至于为什么是luajit,我就不展开了,我们直奔主题,luajit是不支持int_64。但是可以通过ffi,间接的操作int_64。
lua本身也没有官方支持的pb解释器,我们用的是下面这个库:
https://github.com/Neopallium/lua-pb
但是之前这个库在pack int_64的时候有一个BUG(在我们同事提了issue后,刚刚上去看了一下已修复,囧rz。。。)
但我还是把老代码翻出来吧(要不然就写不下去了- -,只能怪自己手慢,再他没更新的时候写就多好啊)
先稍微解释一下这个方法,h存的是高32位,l则是低32位。
每次取7位,然后第八位补1,进行一个递归(如有不懂见上部分)。
理想的是递归4次后,再处理高位。但是有一个问题,就是如果地位是一个比较小的值,于是就可以递归1-3三次后就开始处理高位了,于是就值就出错了。
我们最终的实现是加了一个标志位,记录地位处理次数,强制转4个B。不过还是推荐这个作者最后的实现方式,
local function varint64_next_byte_h_l(h, l) if h ~= 0 then -- encode lower 28 bits. local b1 = bor(band(l, 0x7F), 0x80) local b2 = bor(band(rshift(l, 7), 0x7F), 0x80) local b3 = bor(band(rshift(l, 14), 0x7F), 0x80) local b4 = bor(band(rshift(l, 21), 0x7F), 0x80) -- merg high 32-bits with the last 4 bits from the low 32-bits l = (h * 16) + band(rshift(l, 28), 0xF) -- Use variable length encoding of higher 36 bits. return b1, b2, b3, b4, varint_next_byte(l) end -- No high bits. Use variable length encoding of low bits. return varint_next_byte(l) end