Redis Source Code Read Log( 7 hash 之 ziplist)

Redis hash 命令

hash的命令,大多数都是针对 hash 中的field以及value pair进行的操作。其中 DEL 会直接删除掉整个hash数据结构。

另外,field被清空了的 hash系统也会自动清除掉数据库中的hash key值。

1. hash key 值只支持 string 类型

2. hash key field 只支持 string 类型

3. hash field 中的 value 值,支持 string 以及整数以及浮点型

ziplist 概念介绍

ziplist 是一种具有高效内存利用率的数据结构,是linked list 中的一种(学习下来内部其实是连续的内存空间,并不是所谓的单纯的linked list,linked list是注释中Redis作者自己这么说的)。内中可以存储 字符串以及原始整型数据。ziplist 支持O(1)时间复杂度的 push 以及 pop 操作。

However, because every operation requires a reallocation of the memory used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.

push 以及 pop 都会牵涉对内存的realloc操作。

general 内存布局:

<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

headers

 * <uint32_t zlbytes> is an unsigned integer to hold the number of bytes that the ziplist occupies,

  including the four bytes of the zlbytes field itself.

  This value needs to be stored to be able to resize the entire structure without the need to traverse it first.

这个字段表示整体ziplist占用的总体内存,包含自身以及最终结束标志的一个字节。以字节为单位。

 * <uint32_t zltail> is the offset to the last entry in the list. This allows a pop operation on the far side of the list

  without the need for full traversal.

zltail 指示的是最后一个ziplist entry的相对与ziplist起始指针的字节索引位置。

 * <uint16_t zllen> is the number of entries. When there are more than 2^16-2 entries,

  this value is set to 2^16-1 and we need to traverse the entire list to know how many items it holds.

zllen表示ziplist中的entry的数量,但是,实际只占用2个字节uint16_t类型,也就说最大数是 65535.如果低于 65535,那么指示的entry数量是准确的,如果是65535,就是16个bit全1的情况下,说明长度超了,真实长度,就需要去一个个遍历entry去计数,才能获取到。

 * <uint8_t zlend> is a special entry representing the end of the ziplist.

  Is encoded as a single byte equal to 255. No other normal entry starts with a byte set to the value of 255.

1个字节的8个bit全1的情况,表示结束。

entry

ziplist 的  entry 构造分为 meta-data 部分,以及 payload 部分。

meta-data 部分包含两部分信息:

1. 前一个 entry 的长度,便于 整个 ziplist end to front 端的遍历

2. 编码方式,两种,integer 或者 string

典型 layout

<prevlen> <encoding> <entry-data>

当存储较小整数的时候,encoding 部分可能存储的就是数据本身,此时 entry-data 部分缺失,如下:

<prevlen> <encoding>

entry: prevlen

每一个entry起始一个字节,都表示的是 prevlen。

prevlen < 254: prevlen 自身只占用一个字节为 uint8_t 类型,直接提取即可得到具体的值

prevlen >= 254: prevlen 总体占用 5 个字节。第一个字节为标志位 0XFE, 后面四个字节,小端方式采用 uint32_t 的方式提取值。

此处不会 > 254。因为 255 为结束标记符号。解析时,如果遇到0XFE,表示需要接着往后取4个字节然后以小端方式解析出一个整数,后面才是真正的entry-data。

PS:

entry-data 部分 encoding 字节:

encoding

第一个字节的 高 2 bit 表示编码类型:

0X: string     < 3

11: integer   == 3

获取:

uint8_t ui8;  (ui8 >> 6) & 0x03

注意 encoding:部分,如果高4bit是全1,低4个bit取值为 1 ~ 13,实际表示是 0 ~ 12 这 12 个数,实际 data,这4个bit原生数值减一获取到的。此时,后面是不带 payload 中的 data 的。因为数字已经嵌入到这个 encoding字段了。

entry中的data的获取是依赖于 encoding字段的。

ziplist 示例:

示例:

zlbytes: 4个字节(uint32_t),转成小端 就是  0X00 00 00 0F, 15.

zltail: 4个字节(uint32_t) ,转成小端 就是 0X00 00 00 0C,12.

entries: 2个字节(uint16_t) ,转成小端 就是 0X00 002, 2.

起始元素 00 f3: |0000 0000|1111 0011|

头一个字节,全0, = 0 < 254 表示: prevlen 0,因为是首个entry 前面没有entry

第二个字节,高4bit 1111 而低4bit0011不是全0 或者 全1,也不是1110(后面外带一个字节整数的情形),表示存储的是内置 integer,直接取出来,是3. 需要减一 也就是 2. 说明,第一个 entry 中存储的值是整数 2.(不是所谓的string 2.)

第二个元素 02 f6: |0000 0002|1111 1000|

第一个字节,= 2 < 254,表示 prevlen 2.

第二个字节,高4bit全是1.4bit1000,不是全0 或者 全1 或者 1110。说明内置 integer 在其中。直接取的值为 6.实际需要存储的值就是6 1就是integer 5.

后面1个字节全11表示结束。

注意,需要push一个新的string “Hello World

prevlen 占用 1 个字节,即 entry 5 之后,这个entry长度是两个字节,即 [02]

自身长度,不压缩的情况下是: 11,即 11 byte的纯文本,低于 63 个字节。

那么只需要一个内嵌6bit表示长度即可。

[00 00  0000]

2bit 00,低6bit改成11: 00 1011

[00 00  1011] 0b,结合文本:

[02] [0b] [Hello World]

[02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]

先去去除掉 ff, 插入到队尾,然后再填充一个全1的字节,表示结束即可,更新 zlbytes, zltail 以及 entries 变量。

zlbytes:  15 + 13 = 28

zltail: 12 移动 2个, 14

entries: 2 + 1 = 3

直接按照自身长度复制进入即可,本身就是小端表示的数

实际hash中使用到的就是这种短小的string。如果过长,hash数据结构底层就会发生type conversion,不再使用ziplist而改用dict。这是后话。

当使用ziplist时

优劣

第一:占用内存空间相对较小,空间利用率高。

第二:数据序列化。存入到ziplist中的数据,是自动序列化后的数据,存储在连续的空间中,这样如果持久化,会非常方便。

劣势

第一:查询时间复杂度O(n),而dict是0(1);

第二:修改,删除都相对复杂,需要查询,之后删除或者修改之后,可能需要realloc的操作,需要整体的迁移内存;

第三:增加时,虽然默认追加尾部,但是有 realloc内存的操作。

所以ziplist的使用,首先 entry 不宜过大过多。否则查改删的时间相对较长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值