Redis之listpack、rax

listpack(紧凑列表)

redis5.0引入了一种新的数据结构 listpack,其是对ziplist的改进版本,在存储与结构上都比ziplist要更为节省与精简,listpack目前应用于stream数据结构中。

listpack结构:

struct listpack<T> {
    int32 total_bytes; // 占用的总字节数
    int16 size;        // 元素个数
    T[] entries;       // 元素列表
    int8 end;          // 同ziplist的zlend一样,恒为0xFF
}
复制代码

entries结构如下:

struct lpentry {
    int<var> encoding;
    optional byte[] content;
    int<var> length;
}
复制代码

image.png

listpack与ziplist的不同之处

  1. ziplist通过zltail_offset字段定位最后一个元素的位置,以此用于逆序遍历,但是listpack没有这个字段。

  2. 二者的entries结构基本相同,不过listpack的长度字段放在尾部,并且存储的并不是上一个元素的长度,而是当前元素的长度,因此需要获取最后一个元素位置的时候,可以通过total_bytes字段和最后一个元素的长度计算出来,并不需要额外的zltail_offset字段。

  3. listpack的entries内的元素不再存储上一个元素的长度,所以也就不存在级联更新的问题了。

lpentry的length字段

length长度字段使用varint进行编码,它的长度可以是1~5个字节中任一长度,同UTF8编码,其通过字节的最高位是否为1决定编码的长度,

即如果字节的最高位为1,说明还有一个字节继续表示其长度,直到遇到的字节最高位为0为止。

image.png

lpentry的encoding字段

encoding被设计成了不同的值,用于区分entry中的内容类型,不同情况下,encoding占用的字节大小也不同。

扫描二维码关注公众号,回复: 13671187 查看本文章

字符串

  1. 10xxxxxx(占1字节): 小字符串,长度范围在0~63,content字段里存字符串的内容。

  2. 1110xxxx yyyyyyyy(占2字节): 中等长度字符串,长度范围在0~4095,content字段为字符串内容。

  3. 11110000 aaaaaaaa bbbbbbbb cccccccc dddddddd(占5字节):大字符串,后面4个字节表示长度,content字段中存内容。

整数

  1. 0xxxxxxx(占1字节):非负小整数,范围在 0~127。

  2. 110xxxxx yyyyyyyy(占2字节): 13位有符号整数,范围是 -2048 ~ 2047。

  3. 11110001(占1字节): 16位(2字节)有符号整数,content中存内容。

  4. 11110010(占1字节): 24位(3字节)有符号整数,content中存内容。

  5. 11110011(占1字节): 32位(4字节)有符号整数,content中存内容。

  6. 11110100(占1字节): 64位(8字节)有符号整数,content中存内容。

结束符

  1. 11111111(占1字节): 表示listpack的结束符号 0xFF。

rax(基数树)

rax全称Radix Tree,是一个有序字典树,可以根据key进行排序,支持快速定位、插入与删除,其与hash、zset不同的是,hash不具备排序功能,zset则根据score进行排序。

redis基础结构演化关系:

image.png

应用场景

  1. 英语字典可以看成一颗rax,其单词根据字典序进行排列,每个词汇还有单独的解释,这个解释就是value,单词就是key,有了这棵树可以快速检索单词以及查询某些前缀开头的单词。

image.png

  1. 公安局居民档案信息,key是居民身份证号,value是履历信息,身份证前缀编码根据地区进行一级一级分级,因此可以快速定位具体的居民信息,以及匹配符合前缀的居民信息。

  2. 时间序列应用,时间戳为key,value是时间戳发生的事件,时间戳编码可以根据 年、月、日、时、分、秒、毫秒、微秒、纳秒进行一级一级编排,因此也可以快速定位出某个具体时间发生什么事,以及一个范围内发生了什么事。

  3. web服务器路由,将路由看做一棵树,每个URL对应一个请求处理器,新请求过来时,使用URL与树中的进行匹配,找到对应的请求处理器,因为URL中可能存在正则pattern,而且对同一层的节点顺序没有要求,所以其不是一个严格的rax。

image.png

在redis中的应用

  1. stream

    在redis中,rax被应用于stream结构中的消息队列,stream的消息ID前缀是 时间戳+序号,因此符合时间序列,

    使用rax结构进行存储可以快速根据消息ID定位到其对应的消息,同时可以遍历指定消息之后的消息。

  2. cluster

    在cluster中用于记录槽位与key的对应关系,该对应关系对应的变量名为 slots_to_keys,该raxNode的key由槽位编号hashslot和key组合而成,

    相同hashslot的对象key会被挂在同一个raxNode下,因此可以快速定位某个槽位下的所有对象key,

    cluster的槽位数量是16384,需要2字节表示,所以key的结构是2字节的hashslot和对象key拼接起来的字符串,如下。

image.png

结构

rax下的节点共分为三种类型,根节点、叶子节点、中间节点,有些中间节点带有value,有的中间节点是结构性需要,没有value。

struct raxNode {
    int<1> isKey;         // 是否有key,没key是根节点
    int<1> isNull;        // 没有对应的value,中间节点
    int<1> isCompressed;  // 是否存储压缩
    int<29> size;         // 叶子节点数量 或者是 压缩字符串长度
    byte[] data;          // 用于存储路由键、叶子节点指针、value
}

复制代码

redis中的rax在结构上不是标准的Radix Tree,如果一个中间节点有多个叶子节点,路由键就是一个字符;如果只有一个叶子节点,路由键就是一个字符串。

只有一个叶子节点的时候,就是压缩节点(多个字符压在一起的字符串),下图中的深蓝色节点就是压缩节点:

image.png

raxNode.data 结构

压缩节点

叶子节点只有一个的情况,就是压缩节点。

压缩节点data字段伪代码如下:

struct data {
    optional struct {        // 取决于raxNode的size字段是否为0
        byte[] childKey;     // 路由键
        raxNode* childNode;  // 子节点指针
    } child;
    optional string value;   // 取决于raxNode的isNull字段
}
复制代码

如果压缩节点后面没有节点了,childNode就不存在,如果是中间节点,那么value字段也不存在。

非压缩节点

叶子节点有多个,就不是压缩节点,存在多个路由键,一个键就是一个字符。

struct data {
    byte[] childKeys;       // 路由键字符列表
    raxNode*[] childNodes;  // 多个叶子节点指针
    optional string value;  // 取决于raxNode的isNull字段
}

复制代码

非压缩节点结构:

image.png

猜你喜欢

转载自juejin.im/post/7054478439268483085