redis list底层数据结构

news/2024/7/6 4:43:47

redis list数据结构

 redis list数据结构底层采用压缩列表ziplist或linkedlist两种数据结构进行存储,首先以ziplist进行存储,在不满足ziplist的存储要求后转换为linkedlist列表。
当列表对象同时满足以下两个条件时,列表对象使用ziplist进行存储,否则用linkedlist存储。

  • 列表对象保存的所有字符串元素的长度小于64字节
  • 列表对象保存的元素数量小于512个。

redis list元素添加过程

 list的数据添加根据传入的变量个数一个个顺序添加,整个顺序如下:

  • 创建list对象并添加到db的数据结构当中
  • 针对每个待插入的元素添加到list当中
void pushGenericCommand(redisClient *c, int where) {

    int j, waiting = 0, pushed = 0;

    // 取出列表对象
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]);

    // 如果列表对象不存在,那么可能有客户端在等待这个键的出现
    int may_have_waiting_clients = (lobj == NULL);

    if (lobj && lobj->type != REDIS_LIST) {
        addReply(c,shared.wrongtypeerr);
        return;
    }

    // 将列表状态设置为就绪
    if (may_have_waiting_clients) signalListAsReady(c,c->argv[1]);

    // 遍历所有输入值,并将它们添加到列表中
    for (j = 2; j < c->argc; j++) {

        // 编码值
        c->argv[j] = tryObjectEncoding(c->argv[j]);

        // 如果列表对象不存在,那么创建一个,并关联到数据库
        if (!lobj) {
            lobj = createZiplistObject();
            dbAdd(c->db,c->argv[1],lobj);
        }

        // 将值推入到列表
        listTypePush(lobj,c->argv[j],where);

        pushed++;
    }

    // 返回添加的节点数量
    addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));

    // 如果至少有一个元素被成功推入,那么执行以下代码
    if (pushed) {
        char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";

        // 发送键修改信号
        signalModifiedKey(c->db,c->argv[1]);

        // 发送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
    }

    server.dirty += pushed;
}



 list的每个元素的插入过程中,我们会对是否需要进行转码作两个判断:

  • 对每个插入元素的长度进行判断是否进行ziplist->linkedlist的转码。
  • 对list总长度是否超过ziplist最大长度的判断。
/* 
 * 将给定元素添加到列表的表头或表尾。
 *
 * 参数 where 决定了新元素添加的位置:
 *
 *  - REDIS_HEAD 将新元素添加到表头
 *
 *  - REDIS_TAIL 将新元素添加到表尾
 *
 *
 * 调用者无须担心 value 的引用计数,因为这个函数会负责这方面的工作。
 */
void listTypePush(robj *subject, robj *value, int where) {

    // 是否需要转换编码?
    listTypeTryConversion(subject,value);

    // #define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
    if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
        ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);

    // ZIPLIST
    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
        int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
        // 取出对象的值,因为 ZIPLIST 只能保存字符串或整数
        value = getDecodedObject(value);
        subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
        decrRefCount(value);

    // 双端链表
    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
        if (where == REDIS_HEAD) {
            listAddNodeHead(subject->ptr,value);
        } else {
            listAddNodeTail(subject->ptr,value);
        }
        incrRefCount(value);

    // 未知编码
    } else {
        redisPanic("Unknown list encoding");
    }
}



 判断ziplist中单个元素的长度是否超过64的长度,如果超过了长度那么就需要转编码格式为linkedlist编码。

/* 
 * 对输入值 value 进行检查,看是否需要将 subject 从 ziplist 转换为双端链表,
 * 以便保存值 value 。
 *
 * 函数只对 REDIS_ENCODING_RAW 编码的 value 进行检查,
 * 因为整数编码的值不可能超长。
 */
void listTypeTryConversion(robj *subject, robj *value) {

    // 确保 subject 为 ZIPLIST 编码
    if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;

    if (sdsEncodedObject(value) &&
        // 看字符串是否过长,#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
        sdslen(value->ptr) > server.list_max_ziplist_value)
            // 将编码转换为双端链表
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}


redis ziplist数据结构

 ziplist又叫压缩列表,整体的数据格式如下,暂时可以临时看一看,后面会针对数据结构专门的文章来解释。

/* 
空白 ziplist 示例图

area        |<---- ziplist header ---->|<-- end -->|

size          4 bytes   4 bytes 2 bytes  1 byte
            +---------+--------+-------+-----------+
component   | zlbytes | zltail | zllen | zlend     |
            |         |        |       |           |
value       |  1011   |  1010  |   0   | 1111 1111 |
            +---------+--------+-------+-----------+
                                       ^
                                       |
                               ZIPLIST_ENTRY_HEAD
                                       &
address                        ZIPLIST_ENTRY_TAIL
                                       &
                               ZIPLIST_ENTRY_END

非空 ziplist 示例图

area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|

size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
            +---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
            +---------+--------+-------+--------+--------+--------+--------+-------+
                                       ^                          ^        ^
address                                |                          |        |
                                ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                  |
                                                        ZIPLIST_ENTRY_TAIL
*/
/*
 * 保存 ziplist 节点信息的结构
 */
typedef struct zlentry {

    // prevrawlen :前置节点的长度
    // prevrawlensize :编码 prevrawlen 所需的字节大小
    unsigned int prevrawlensize, prevrawlen;

    // len :当前节点值的长度
    // lensize :编码 len 所需的字节大小
    unsigned int lensize, len;

    // 当前节点 header 的大小
    // 等于 prevrawlensize + lensize
    unsigned int headersize;

    // 当前节点值所使用的编码类型
    unsigned char encoding;

    // 指向当前节点的指针
    unsigned char *p;

}


redis linkedlist数据结构

 双向列表的格式是通用的数据结构,不用过多解释大家都能理解了。

/*
 * 双端链表节点
 */
typedef struct listNode {

    // 前置节点
    struct listNode *prev;

    // 后置节点
    struct listNode *next;

    // 节点的值
    void *value;

} listNode;

/*
 * 双端链表迭代器
 */
typedef struct listIter {

    // 当前迭代到的节点
    listNode *next;

    // 迭代的方向
    int direction;

} listIter;

/*
 * 双端链表结构
 */
typedef struct list {

    // 表头节点
    listNode *head;

    // 表尾节点
    listNode *tail;

    // 节点值复制函数
    void *(*dup)(void *ptr);

    // 节点值释放函数
    void (*free)(void *ptr);

    // 节点值对比函数
    int (*match)(void *ptr, void *key);

    // 链表所包含的节点数量
    unsigned long len;

} list;

http://www.niftyadmin.cn/n/4225157.html

相关文章

C#小结(一)

经过这段时间对C#的学习&#xff0c;对C#有了初步的了解。下面让我们一起走进C#之旅。 基本概念&#xff1a; .net/dotnet&#xff1a;一般指.Net Framework框架。一种平台&#xff0c;一种技术。 C#&#xff08;sharp&#xff09;&#xff1a;一种编程语言&#xff0c;可以…

中关村攻略

说实话&#xff0c;在伟大的祖国&#xff0c;很多事情都是很神奇的&#xff0c;能神奇到你无法想象的地步&#xff0c;比如去中关村买东西。在这个神奇的世界里&#xff0c;没有啥条款能保证你的利益&#xff0c;你被骗&#xff0c;别人只能怪你没做好功课&#xff0c;而如果你…

C#小结(二)

前言&#xff1a; 最基础的知识往往就是我们最值得重视的东西&#xff0c;它们是构成知识大厦的基石。 转义符&#xff1a; 常用的转义字符&#xff1a; \:一个字符&#xff0c;组成转义字符。一般用于表示特殊符号&#xff1b; \n:表示换行&#xff1b; \b:表示退格&…

马天宇的音乐

一个年龄相差无几的青年歌手 值的尊敬啊 最喜欢他的一首"坚强" 很有爱....

C#总结(三)

前言&#xff1a; 泛型是C#2.0的一个新增加的特性&#xff0c;是C#编程中不可缺少的部分。它是程序设计语言的一种特性&#xff0c;为使用C#语言编写面向对象程序增加了极大的效力和灵活性。 集合--ArrayList(): ArrayList():可以放各种类型的数据&#xff0c;并且不确定放多…

无耻的微软

其实满奇怪。用微软的系统&#xff0c;用微软的浏览器。用微软的输入法。我还在骂微软。 今天碰到一件非常常见的事情&#xff0c;我的移动硬盘突然了。虽然我这个人经常有备份重要数据的习惯。不过突然移动硬盘总不是个爽的事情。插了N台机器&#xff0c;统统不好使。硬盘感觉…

针对nginx应用场景的配置 知识整理

本文为转载&#xff0c;原文链接 前言 原本想写整理一篇针对nginx应用场景的相应配置&#xff0c;但发现已经有人整理了&#xff0c;而且写得非常不错&#xff0c;特意转过来 概论 Nginx 是一款面向性能设计的 HTTP 服务器&#xff0c;能反向代理 HTTP&#xff0c;HTTPS 和邮件…

命令提示符之Windows命令(cmd.exe)

前言&#xff1a; 这周六来到了我们新的学习环境&#xff0c;一切都是那么新鲜&#xff0c;在这儿的学习从——执行批处理过程开始。按照师姐发给我们的文档一步一步执行&#xff0c;前面一切很顺利&#xff0c;当执行到第三步—放置和运行批处理文件时遇到问题&#xff0c;无…