03 关于 zipmap

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了03 关于 zipmap相关的知识,希望对你有一定的参考价值。

前言

关于 redis 的数据结构 zipmap 

相关介绍主要围绕着如下测试用例, 来看看 zipmap 的存储, 以及 相关的 api 

本文的 zipmap 相关代码 拷贝自 redis-6.2.0  

代码来自于 https://redis.io/ 

 

这个数据结构在 redis 中已经没有使用了, 呵呵 

但是在几年前 我看过的一个的一个版本里面, 它应该是作为了 hash 的底层支撑之一吧 

现在看了一下 hash 的实现, 似乎是 hashtable / ziplist 来支撑业务了 

不过 zipmap 还是可以了解一下的, 相比于 ziplist 这里的设计简单的多 

 

 

测试用例

//
// Created by Jerry.X.He on 2021-02-21.
//


#include <iostream>
#include "../libs/sds.h"
#include "../libs/zipmap.h"

using namespace std;

// Test25ZipListUsage
int main(int argc, char **argv) {

    unsigned char *zm = zipmapNew();

    sds firstKey = sdsnew("name");
    sds firstValue = sdsnew("jerry");
    sds secondKey = sdsnew("age");
    sds secondValue = sdsnew("74");

    // zipmapSet
    int updated = 0;
    zm = zipmapSet(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey),
                   (unsigned char *) firstValue, (unsigned int) sdslen(firstValue), &updated);
    zm = zipmapSet(zm, (unsigned char *) secondKey, (unsigned int) sdslen(secondKey),
                   (unsigned char *) secondValue, (unsigned int) sdslen(secondValue), &updated);

    int len = zipmapLen(zm);

    // zipmapGet
    unsigned char *nameValue = NULL;
    unsigned int nameValueLen = 0;
    zipmapGet(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey),
              &nameValue, &nameValueLen);

    // zipmapExists
    int existsResult = zipmapExists(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey));

    // zipmapNext
    unsigned char *entryKey = NULL;
    unsigned char *entryValue = NULL;
    unsigned int entryKeyLen = 0, entryValueLen = 0;
    unsigned char *i = zipmapRewind(zm);
    while ((i = zipmapNext(i, &entryKey, &entryKeyLen, &entryValue, &entryValueLen)) != NULL) {
        printf("%d bytes key at %s \\n", entryKeyLen, entryKey);
        printf("%d bytes value at %s \\n", entryValueLen, entryValue);
    }

    // zipDelete
    int deleted = 0;
    zm = zipmapDel(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey), &deleted);
    int newLen = zipmapLen(zm);

    // zipmapBlobLen
    int zmBytes = zipmapBlobLen(zm);

    int x = 0;

}

 

 

数据结构 

zipmap 的内存结构大致如下 <zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>

zmlen : 是第一个字节, 用于表示 zipmap 的长度 

<len>"foo"<len><free>"bar" : 表示一个 entry(key + value), key 是 len + 数据, value 是 len + free + 数据 

zmend 一字节, 0xff 标记 zipmap 结尾标记[类似于 string 约束 '\\0' 为结束符]

 

 

zipmapNew 

分配了 两个字节, 1个字节表示长度, 另外一个字节为结束标记 

 

 

zipmapSet

计算保存 key, value 需要的长度, 标记为 len
从整个 zipmap 中查询 key 对应的 entry, 标记为 p
如果 entry 存在, 如果原有 entry 的 oldLen 小于当前 len, 扩容zipmap, 增加 (len-oldLen), 移动 p 后面的数据
如果 entry 不存在, 扩容zipmap 增加 len, 增加 zipmap 的长度
如果 (len - oldLen) > ZIPMAP_VALUE_MAX_FREE[默认为4], 缩容zipmap, 减少(len-oldLen)个字节, 移动 p后面的数据
写入当前 entry 的 keyLen, key, valueLen, freeLen, value

 

执行了 "zm = zipmapSet(zm, (unsigned char *) firstKey, (unsigned int) sdslen(firstKey), (unsigned char *) firstValue, (unsigned int) sdslen(firstValue), &updated);" 之后

我们来 inspect 一下 zm 

zmLen 为 1 

接着为第一个 entry 的 key, 长度为 0x04, 接着四个字节为 key 的数据 "name" 

接着为第一个 entry 的 value, 长度为 0x05, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "jerry"  

接着为 0x7fb5f850006d 位置上的 0xff 为 zmend zipmap 的结束标记 

(lldb) x 0x7fb5f8500060
0x7fb5f8500060: 01 04 6e 61 6d 65 05 00 6a 65 72 72 79 ff 00 50  ..name..jerry�.P
0x7fb5f8500070: 37 00 85 5f fb 07 00 c0 00 00 00 00 00 00 00 50  7.._�..�.......P

 

执行了 "zm = zipmapSet(zm, (unsigned char *) secondKey, (unsigned int) sdslen(secondKey), (unsigned char *) secondValue, (unsigned int) sdslen(secondValue), &updated);" 之后 

我们来 inspect 一下 zm 

zmLen 为 2 

接着为第一个 entry 的 key, 长度为 0x04, 接着四个字节为 key 的数据 "name" 

接着为第一个 entry 的 value, 长度为 0x05, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "jerry" 

接着为第二个 entry 的 key, 长度为 0x03, 接着四个字节为 key 的数据 "age" 

接着为第二个 entry 的 value, 长度为 0x02, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "74" 

接着为 0x7fb5f8500075 位置上的 0xff 为 zmend zipmap 的结束标记 

(lldb) x 0x7fb5f8500060
0x7fb5f8500060: 02 04 6e 61 6d 65 05 00 6a 65 72 72 79 03 61 67  ..name..jerry.ag
0x7fb5f8500070: 65 02 00 37 34 ff 00 c0 00 00 00 00 00 00 00 50  e..74�.�.......P

 

 

zipmapLen

从 zipmap 的头部获取长度 

如果 超过了 254, 则遍历 zipmap 来计算长度 

 

 

zipmapGet

从整个 zipmap 中查询 key 对应的 entry, 标记为 p
将 value, valueLen 存入 接受指针

 

 

zipmapExists

遍历整个 zipmap 中查询 key 对应的 entry, 标记为 p
如果存在, exists
 

 

 

zipmapRewind

跳过 zmlen, 从第一个元素开始迭代 

 

zipmapNext 

获取 zm 所在的 entry 的 key, value 的信息 

并迭代 zm 到下一个 entry 的位置, 返回 

 

 

zipmapDel

从整个 zipmap 中查询 key 对应的 entry, 标记为 p
如果不存在, 标记未删除
否则 获取 p 对应的 entry 的长度, 移动 p 之后的节点
缩容 zipmap, 更新 length, 更新删除标记

 

 

我们来 inspect 一下 zm 

zmLen 为 1 

接着为第一个 entry 的 key, 长度为 0x03, 接着四个字节为 key 的数据 "age" 

接着为第一个 entry 的 value, 长度为 0x02, 空闲字符为 0x00 个, 接着五个字节为 value 的数据 "74" 

接着为 0x7fb5f8500069 位置上的 0xff 为 zmend zipmap 的结束标记 

(lldb) x 0x7fb5f8500060
0x7fb5f8500060: 01 03 61 67 65 02 00 37 34 ff 72 72 79 03 61 67  ..age..74�rry.ag
0x7fb5f8500070: 65 02 00 37 34 ff 00 c0 00 00 00 00 00 00 00 50  e..74�.�.......P

 

 

zipmapBlobLen

借用了 zipmapLookupRaw 迭代来计算 zipmap 占用的字节数 

 

 

zipmapRepr

输出 zipmap 的头数据信息, 输出每一个 entry 的相关数据信息 

 

如上面 zm 输出结果如下 

为了更深入的理解各个 zipmap 这个数据结构, 初学还是建议直接查看内存信息来剖析 

{status 1}{key 3}age{value 2}74{end}

 

 

完 

 

 

以上是关于03 关于 zipmap的主要内容,如果未能解决你的问题,请参考以下文章

条件表达式中的 zipmap 函数给出真假结果表达式必须具有一致的类型错误

Redis源码剖析 - Redis内置数据结构之压缩字典zipmap

Redis学习笔记2--Redis数据存储优化机制

如何从片段返回主要活动

关于代码片段的时间复杂度

关于片段生命周期