Redis ziplist源码解析

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

size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
            +---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
            +---------+--------+-------+--------+--------+--------+--------+-------+
            +--------------+------------+---------------+
entry       |    prevlen   |  encoding  | data          | 
            +--------------+------------+---------------+

                       |<-前一项长度 ->|<-当前编码->|<-存储的数据  ->|

在这里插入图片描述

编码过程:

entry存储的键值对会根据数据进行转码,尝试转化为整数编码,这样可以节省更多的空间。

各个版本差异比较大,这里参考的是redis3.0版本
/*
 * 整数编码类型
 */
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe

/* 4 bit integer immediate encoding 
 *
 * 4 位整数编码的掩码和类型
 */
#define ZIP_INT_IMM_MASK 0x0f
#define ZIP_INT_IMM_MIN 0xf1    /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd    /* 11111101 */
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)

/* Check if string pointed to by 'entry' can be encoded as an integer.
 * Stores the integer value in 'v' and its encoding in 'encoding'. 
 *
 * 检查 entry 中指向的字符串能否被编码为整数。
 *
 * 如果可以的话,
 * 将编码后的整数保存在指针 v 的值中,并将编码的方式保存在指针 encoding 的值中。
 *
 * 注意,这里的 entry 和前面代表节点的 entry 不是一个意思。
 *
 * T = O(N)
 */
static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
    long long value;

    // 忽略太长或太短的字符串
    if (entrylen >= 32 || entrylen == 0) return 0;

    // 尝试转换
    // T = O(N)
    if (string2ll((char*)entry,entrylen,&value)) {

        /* Great, the string can be encoded. Check what's the smallest
         * of our encoding types that can hold this value. */
        // 转换成功,以从小到大的顺序检查适合值 value 的编码方式
        if (value >= 0 && value <= 12) {
            *encoding = ZIP_INT_IMM_MIN+value;
        } else if (value >= INT8_MIN && value <= INT8_MAX) {
            *encoding = ZIP_INT_8B;
        } else if (value >= INT16_MIN && value <= INT16_MAX) {
            *encoding = ZIP_INT_16B;
        } else if (value >= INT24_MIN && value <= INT24_MAX) {
            *encoding = ZIP_INT_24B;
        } else if (value >= INT32_MIN && value <= INT32_MAX) {
            *encoding = ZIP_INT_32B;
        } else {
            *encoding = ZIP_INT_64B;
        }

        // 记录值到指针
        *v = value;

        // 返回转换成功标识
        return 1;
    }

    // 转换失败
    return 0;
}

缺点:

查找性能差

查找一块连续内存区域,当储存的元素过多时,需要从头到尾去遍历,每一个entry都需要编码过程,十分耗时

/* Find pointer to the entry equal to the specified entry. 
 * 
 * 寻找节点值和 vstr 相等的列表节点,并返回该节点的指针。
 * 
 * Skip 'skip' entries between every comparison. 
 *
 * 每次比对之前都跳过 skip 个节点。
 *
 * Returns NULL when the field could not be found. 
 *
 * 如果找不到相应的节点,则返回 NULL 。
 *
 * T = O(N^2)
 */
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {
    int skipcnt = 0;
    unsigned char vencoding = 0;
    long long vll = 0;

    // 只要未到达列表末端,就一直迭代
    // T = O(N^2)
    while (p[0] != ZIP_END) {
        unsigned int prevlensize, encoding, lensize, len;
        unsigned char *q;

        ZIP_DECODE_PREVLENSIZE(p, prevlensize);
        ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
        q = p + prevlensize + lensize;

        if (skipcnt == 0) {

            /* Compare current entry with specified entry */
            // 对比字符串值
            // T = O(N)
            if (ZIP_IS_STR(encoding)) {
                if (len == vlen && memcmp(q, vstr, vlen) == 0) {
                    return p;
                }
            } else {
                /* Find out if the searched field can be encoded. Note that
                 * we do it only the first time, once done vencoding is set
                 * to non-zero and vll is set to the integer value. */
                // 因为传入值有可能被编码了,
                // 所以当第一次进行值对比时,程序会对传入值进行解码
                // 这个解码操作只会进行一次
                if (vencoding == 0) {
                    if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {
                        /* If the entry can't be encoded we set it to
                         * UCHAR_MAX so that we don't retry again the next
                         * time. */
                        vencoding = UCHAR_MAX;
                    }
                    /* Must be non-zero by now */
                    assert(vencoding);
                }

                /* Compare current entry with specified entry, do it only
                 * if vencoding != UCHAR_MAX because if there is no encoding
                 * possible for the field it can't be a valid integer. */
                // 对比整数值
                if (vencoding != UCHAR_MAX) {
                    // T = O(1)
                    long long ll = zipLoadInteger(q, encoding);
                    if (ll == vll) {
                        return p;
                    }
                }
            }

            /* Reset skip count */
            skipcnt = skip;
        } else {
            /* Skip entry */
            skipcnt--;
        }

        /* Move to next entry */
        // 后移指针,指向后置节点
        p = q + len;
    }

    // 没有找到指定的节点
    return NULL;
}

连锁更新性能问题

更新或者删除某元素时,需要重新计算所需空间大小并且重新分配所需要的空间,性能肯定是不行的。详细请看下面ziplist增加元素的做法,一个 ziplist 元素包括了 prevlen(编码需要改变)、encoding 和实际数据 data 三个部分

/* Insert item at "p". */
/*
 * 根据指针 p 所指定的位置,将长度为 slen 的字符串 s 插入到 zl 中。
 *
 * 函数的返回值为完成插入操作之后的 ziplist
 *
 * T = O(N^2)
 */
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    // 记录当前 ziplist 的长度
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = 0;
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0;
    long long value = 123456789; /* initialized to avoid warning. Using a value
                                    that is easy to see if for some reason
                                    we use it uninitialized. */
    zlentry entry, tail;

    /* Find out prevlen for the entry that is inserted. */
    if (p[0] != ZIP_END) {
        // 如果 p[0] 不指向列表末端,说明列表非空,并且 p 正指向列表的其中一个节点
        // 那么取出 p 所指向节点的信息,并将它保存到 entry 结构中
        // 然后用 prevlen 变量记录前置节点的长度
        // (当插入新节点之后 p 所指向的节点就成了新节点的前置节点)
        // T = O(1)
        entry = zipEntry(p);
        prevlen = entry.prevrawlen;
    } else {
        // 如果 p 指向表尾末端,那么程序需要检查列表是否为:
        // 1)如果 ptail 也指向 ZIP_END ,那么列表为空;
        // 2)如果列表不为空,那么 ptail 将指向列表的最后一个节点。
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END) {
            // 表尾节点为新节点的前置节点

            // 取出表尾节点的长度
            // T = O(1)
            prevlen = zipRawEntryLength(ptail);
        }
    }

    /* See if the entry can be encoded */
    // 尝试看能否将输入字符串转换为整数,如果成功的话:
    // 1)value 将保存转换后的整数值
    // 2)encoding 则保存适用于 value 的编码方式
    // 无论使用什么编码, reqlen 都保存节点值的长度
    // T = O(N)
    if (zipTryEncoding(s,slen,&value,&encoding)) {
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipEncodeLength will use the
         * string length to figure out how to encode it. */
        reqlen = slen;
    }
    /* We need space for both the length of the previous entry and
     * the length of the payload. */
    // 计算编码前置节点的长度所需的大小
    // T = O(1)
    reqlen += zipPrevEncodeLength(NULL,prevlen);
    // 计算编码当前节点值所需的大小
    // T = O(1)
    reqlen += zipEncodeLength(NULL,encoding,slen);

    /* When the insert position is not equal to the tail, we need to
     * make sure that the next entry can hold this entry's length in
     * its prevlen field. */
    // 只要新节点不是被添加到列表末端,
    // 那么程序就需要检查看 p 所指向的节点(的 header)能否编码新节点的长度。
    // nextdiff 保存了新旧编码之间的字节大小差,如果这个值大于 0 
    // 那么说明需要对 p 所指向的节点(的 header )进行扩展
    // T = O(1)
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;

    /* Store offset because a realloc may change the address of zl. */
    // 因为重分配空间可能会改变 zl 的地址
    // 所以在分配之前,需要记录 zl 到 p 的偏移量,然后在分配之后依靠偏移量还原 p 
    offset = p-zl;
    // curlen 是 ziplist 原来的长度
    // reqlen 是整个新节点的长度
    // nextdiff 是新节点的后继节点扩展 header 的长度(要么 0 字节,要么 4 个字节)
    // T = O(N)
    zl = ziplistResize(zl,curlen+reqlen+nextdiff);
    p = zl+offset;

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        // 新元素之后还有节点,因为新元素的加入,需要对这些原有节点进行调整

        /* Subtract one because of the ZIP_END bytes */
        // 移动现有元素,为新元素的插入空间腾出位置
        // T = O(N)
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* Encode this entry's raw length in the next entry. */
        // 将新节点的长度编码至后置节点
        // p+reqlen 定位到后置节点
        // reqlen 是新节点的长度
        // T = O(1)
        zipPrevEncodeLength(p+reqlen,reqlen);

        /* Update offset for tail */
        // 更新到达表尾的偏移量,将新节点的长度也算上
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        /* When the tail contains more than one entry, we need to take
         * "nextdiff" in account as well. Otherwise, a change in the
         * size of prevlen doesn't have an effect on the *tail* offset. */
        // 如果新节点的后面有多于一个节点
        // 那么程序需要将 nextdiff 记录的字节数也计算到表尾偏移量中
        // 这样才能让表尾偏移量正确对齐表尾节点
        // T = O(1)
        tail = zipEntry(p+reqlen);
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else {
        /* This element will be the new tail. */
        // 新元素是新的表尾节点
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    /* When nextdiff != 0, the raw length of the next entry has changed, so
     * we need to cascade the update throughout the ziplist */
    // 当 nextdiff != 0 时,新节点的后继节点的(header 部分)长度已经被改变,
    // 所以需要级联地更新后续的节点
    if (nextdiff != 0) {
        offset = p-zl;
        // T  = O(N^2)
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    /* Write the entry */
    // 一切搞定,将前置节点的长度写入新节点的 header
    p += zipPrevEncodeLength(p,prevlen);
    // 将节点值的长度写入新节点的 header
    p += zipEncodeLength(p,encoding,slen);
    // 写入节点值
    if (ZIP_IS_STR(encoding)) {
        // T = O(N)
        memcpy(p,s,slen);
    } else {
        // T = O(1)
        zipSaveInteger(p,value,encoding);
    }

    // 更新列表的节点数量计数器
    // T = O(1)
    ZIPLIST_INCR_LENGTH(zl,1);

    return zl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/224674.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多人聊天UDP

服务端 package 多人聊天;import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList;…

第十六届山东省职业院校技能大赛中职组网络安全赛项竞赛正式试题

第十六届山东省职业院校技能大赛中职组网络安全"赛项竞赛试题 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛阶段 竞赛阶段任务阶段竞赛任务竞赛时间分值A、B模块A-1登录安全加固180分钟200分A-2本地安全策略设置A-3流量完整性保护A-4事件监控A-5服务加固A-6防火墙策…

Error: Could not create the Java Virtual Machine(Linux启动tomcat成功后找不到进程8080端口)

文章目录 问题解决问题过程Tomcat版本要求 问题解决 版本冲突&#xff0c;我的jdk是1.8.x&#xff0c; tomcat 是 10.1.x的&#xff0c;要求jdk是11。 问题过程 运行 ./startup.sh 显示如下&#xff1a; 还以为运行成功呢&#xff0c; 使用命令一查&#xff0c;根本查不到进…

C语言进阶之路之内存镜像与字符操作函数篇

目录 一、学习目标&#xff1a; 二、内存镜像 什么是进程 C进程内存布局 栈内存 静态数据 数据段&#xff08;存储静态数据&#xff09;与代码段 堆内存 三、字符操作函数 函数strstr 函数strlen strlen与sizeof的区别 函数strtok 函数strcat与strncat 函数strc…

记录 | python向上取整和向下取整

python 向上取整和向下取整 向上取整 使用 ceil()&#xff1a; import mathx 0.55 x0 math.ceil(x)向下取整 使用 floor()&#xff1a; import mathx 0.55 x1 math.floor(x)如下&#xff1a;

群聊Java

服务端 import java.io.*; import java.net.*; import java.util.ArrayList; public class Server{public static ServerSocket server_socket;public static ArrayList<Socket> socketListnew ArrayList<Socket>(); public static void main(String []args){try{…

VR远程带看,助力线下门店线上化转型“自救”

VR远程带看&#xff0c;因自身高效的沉浸式在线沟通功能&#xff0c;逐渐走进了大众的视野。身临其境的线上漫游体验以及实时同屏互联的新型交互模式&#xff0c;提升了商家同用户之间的沟通效率&#xff0c;进一步实现了远程线上一对一、一对多的同屏带看&#xff0c;用户足不…

封装校验-----Vue3+ts项目

登录校验页面 <script setup lang"ts"> import { ref } from vue import { mobileRules, passwordRules } from /utils/rules const mobile ref() const password ref() </script><!-- 表单 --><van-form autocomplete"off">&l…

使用Draw.io制作泳道图

使用Draw.io制作泳道图 一、横向泳道图1. 有标题泳道图2. 无标题泳道图3. 横纵向扩展泳道 二、纵向泳道图三、横纵交错地泳道图想做这样的图具体步骤1. 拖拽一个带标题的横向泳道图2. 拖拽一个带标题的单一图&#xff0c;并且把它放进Lane1中3. 其他注意 四、下载文件说明 一、…

CSS的逻辑组合伪类

CSS 的逻辑组合伪类有 4 种&#xff0c;分别是&#xff1a;:not()、:is()、:where()和&#xff1a;has()。 否定伪类:not() :not 伪类选择器用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中&#xff0c;它也被称为反选伪类&#xff08;negation pseudo-…

走迷宫(详细分析)

目录 一、课题描述 输入样例&#xff1a; 输出样例&#xff1a; 二、需求分析 输入的形式和输入值的范围&#xff1a; 输出的形式&#xff1a; 程序所能达到的功能&#xff1a; 三、概要设计 四、流程图 五 、代码详细注释 六、测试数据和结果 一、课题描述 以一个…

2024年江苏省职业院校技能大赛信息安全管理与评估 第三阶段学生组(样卷)

2024年江苏省职业院校技能大赛信息安全管理与评估 第三阶段学生组&#xff08;样卷&#xff09; 竞赛项目赛题 本文件为信息安全管理与评估项目竞赛-第三阶段样题&#xff0c;内容包括&#xff1a;网络安全渗透、理论技能与职业素养。 本次比赛时间为180分钟。 介绍 GeekSe…

上海亚商投顾:沪指窄幅震荡 多只高位股午后跳水

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日窄幅震荡&#xff0c;创业板指冲高回落。锂电池板块集体反弹&#xff0c;西藏矿业、吉翔股份、永兴材…

01-Redis核心数据结构与高性能原理

一、Redis的单线程和高性能 1. Redis是单线程吗&#xff1f; Redis的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的&#xff08;说白了也就是执行命令的时候是由一个线程来完成的&#xff09;&#xff0c;这也是 Redis 对外提供键值存储服务的主要流程。…

基于epoll实现Reactor服务器

了解epoll底层逻辑 在我们调用epoll_create的时候会创建出epoll模型&#xff0c;这个模型也是利用文件描述类似文件系统的方式控制该结构。 在我们调用epoll_create的时候&#xff0c;就会在内核管理中创建一个epoll模型&#xff0c;并且建管理模块地址给file结构体&#xff…

python数据分析基础

前言 2023年10月以来&#xff0c;一位在商学院就读的可爱同学遇上了一门课——python数据分析&#xff0c;并遇到了许多问题&#xff0c;找上了我&#xff0c;就此&#xff0c;我也开始了学习之路&#xff0c;虽然很浅显&#xff0c;但这些东西对部门同学来说也是受用的&#…

TypeScript中的单件设计模式

基本概念 &#xff08;1&#xff09; 了解设计模式 设计模式通俗的讲&#xff0c;就是一种更好的编写代码方案&#xff0c;打个比喻&#xff1a;从上海到武汉&#xff0c;你可以选择做飞机&#xff0c;做轮船&#xff0c;开车&#xff0c;骑摩托车多种方式&#xff0c;把出行…

短视频购物系统源码:构建创新购物体验的技术深度解析

短视频购物系统作为电商领域的新宠&#xff0c;其背后的源码实现是其成功的关键。本文将深入探讨短视频购物系统的核心技术和源码设计&#xff0c;以揭示其如何构建创新购物体验的技术奥秘。 1. 技术架构与框架选择 短视频购物系统的源码首先考虑的是其技术架构。常见的选择…

ExoPlayer架构详解与源码分析(10)——H264Reader

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

AI大模型:启动参数总结整理

虽然通过调整启动大模型的参数&#xff0c;对生成效果的提升是有限的&#xff0c;但适当的调整&#xff0c;还是能满足一些常用的场景的~ 一. 【max_length】 令牌生成最大数 用于控制生成文本的最大长度&#xff0c;默认为 20。它的值对应于输入提示的长度加上max_new_token…
最新文章