redis源码解析(四)——ziplist

版本:redis - 5.0.4
参考资料:redis设计与实现
文件:src下的ziplist.c ziplist.h

    • 一、基础知识
        • 1、压缩列表的各个组成部分及详细说明
        • 2、列表节点
        • 3、encoding
    • 二、连锁更新
    • 三、ziplist.h
    • quickList

一、基础知识

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序性数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值(短字符串小的整数)。
约等于连续内存的双向链表,使用p指针+长度寻址,数据过多时查询性能差

1、压缩列表的各个组成部分及详细说明

ziplist是由以下几部分组成:
| zlbytes | zltail | zllen | entry1 | entry2 | … | entryN | zlend|

 *  [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
 *        |             |          |       |       |     |
 *     zlbytes        zltail     zllen    "2"     "5"   end
属性类型长度用途
zlbytesuint32_t4字节记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配,或者计算zlend的位置时使用
zltailuint32_t4字节记录压缩列表表尾节点距离压缩列表的起始地址有多少个字节:通过这个偏移量,程序无需遍历整个压缩列表就可以确定表尾节点的地址
zllenuint16_t2字节记录了压缩列表包含的节点数量
entryN列表节点不定压缩列表包含的各个节点,节点的长度由节点保存的内存决定
zlenduint8_t1字节特殊值oxFF(十进制255),用于标记压缩列表的末尾

2、列表节点

typedef struct zlentry {
    unsigned int prevrawlensize;//前一节点encoding的长度
    unsigned int prevrawlen;//前一个节点的长度,小于254 为一个字节,大于则为五个字节
    unsigned int lensize;//当前节点encoding的长度(byte)
    unsigned int len;//当前节点长度,使用了多少byte
    unsigned int headersize;//prevrawlensize + lensize
    unsigned char encoding; /* 数据类型ZIP_STR_* 或ZIP_INT_* 编码格式
    							However for 4 bits immediate integers this can assume a range of values and must be range-checked. */
    unsigned char *p;//指向每一个节点的开始,也就是prevrawlen的位置
} zlentry;
  • 存储长度的数值采用小端字节序
  • 每个节点大小不同
  • 由于存储了每个节点的字节数,无需遍历,用指针p加减字节数,就能让p指向自己需要的地址空间,获得需要的值。

3、encoding

ziplist节约内存很重要的方式就是encoding,我们要弄清楚encoding是什么:

encoding:记录了节点所保存数据的类型以及长度。

  • 一字节、两字节或者五字节长,值最高位为00、01、或者10是字节数组编码:这种编码表示节点保存着字节数组,数组长度由编码去掉最高两位的其他位记录。
  • 一字节长,值的最高位为11的是整数编码:表示保存的是整数值,整数值类型和长度由编码去除最高两位的其他位记录。

字节数组编码

编码编码长度保存的值
00bbbbbb1 字节长度小于等于 63 字节的字节数组
01bbbbbb xxxxxxxx2 字节长度小于等于 16383 字节的字节数组
10______ aaaaaaaa bbbbbbbb cccccccc dddddddd5 字节长度小于等于 4294967295 的字节数组

整数编码

编码编码长度保存的值
110000001 字节int16_t 类型的整数
110100001 字节int32_t 类型的整数
111000001 字节int64_t 类型的整数
111100001 字节24 位有符号整数
111111101 字节8 位有符号整数
1111xxxx1 字节无, 因为编码本身的 xxxx 四个位已经保存了一个介于 0 和12 之间的值

二、连锁更新

前提
每个节点都存储了前一个节点的长度;
根据节点长度不同选择不同字节大小的空来存储长度信息(节省空间);

情景:假设有一个ziplist,存储了几个长度在250到253字节(next的prelen只需要1字节)的节点(只有这几个节点)。现在插入一个大于等于254字节(next的prelen需要5字节)大小的新节点在ziplist头部。
在这里插入图片描述
结果
node1的prelen只有一个字节大小,不够,需要重新申请空间,变成五个字节。申请之后,node1大小超过254,也需要它的下一个节点也需要五个字节来存储它的长度,即node2。
同理,node2申请空间,扩大,则node3也需要,之后node4,node5都需要。
我们只是插入了一个新节点,但是整个list都改变了,每个节点都要重新申请空间。这种连续的多次空间扩展称之为连锁更新

连锁更新出现的要求
连续的,多个,长度介于250到253字节的节点;
概率很低,所以连锁更新对ziplist的影响不大

/*
  插入节点时, 我们需要将下一个节点的前一节点长度字段设置值为插入节点的长度。
  可能会发生这种情况:下一个节点的prelen字段需要增长, 1字节->5字节。
  这只发生在有节点插入的情况下 (会导致 realloc 和 memmove)。
  当有连续节点大小接近zip _ big _ prvlen时, 这种效果可能会在ziplist中级联,

  注意:这种效果也可能发生在反向, 其中prevlen字段所需的字节可能会缩小。
  链式的节点 先增长,再缩小会导致频繁的空间resize,因此prevlen字段缩小的情况被故意忽略,
  字段长度允许保持大于必要的字节,。

  指针p 指向不需要插入后的第一个节点。
 */
unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;
    size_t offset, noffset, extra;
    unsigned char *np;
    zlentry cur, next;

    //链表不为空
    while (p[0] != ZIP_END) {
        //获取p所指向的节点的全部信息,存在cur指向的空间中
        zipEntry(p, &cur);
        rawlen = cur.headersize + cur.len;
        rawlensize = zipStorePrevEntryLength(NULL,rawlen);

        //没有下一个节点,结束
        if (p[rawlen] == ZIP_END) break;
        //有下一个节点,取得信息,放在next中
        zipEntry(p+rawlen, &next);

        //判断next的prevrawlen是否被更改:没有(不发生更新),结束
        if (next.prevrawlen == rawlen) break;

        //prevrawlen字段需要扩展
        if (next.prevrawlensize < rawlensize) {
            /* The "prevlen" field of "next" needs more bytes to hold
             * the raw length of "cur". */
            offset = p-zl;
            extra = rawlensize-next.prevrawlensize;
            zl = ziplistResize(zl,curlen+extra);
            p = zl+offset;

            /* Current pointer and offset for next element. */
            np = p+rawlen;
            noffset = np-zl;

            /* Update tail offset when next element is not the tail element. */
            if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {
                ZIPLIST_TAIL_OFFSET(zl) =
                    intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
            }

            /* Move the tail to the back. */
            memmove(np+rawlensize,
                np+next.prevrawlensize,
                curlen-noffset-next.prevrawlensize-1);
            zipStorePrevEntryLength(np,rawlen);

            /* Advance the cursor */
            p += rawlen;
            curlen += extra;
        } else {//需要紧缩
            if (next.prevrawlensize > rawlensize) {
                /* This would result in shrinking, which we want to avoid.
                 * So, set "rawlen" in the available bytes. */
                zipStorePrevEntryLengthLarge(p+rawlen,rawlen);
            } else {
                zipStorePrevEntryLength(p+rawlen,rawlen);
            }

            /* Stop here, as the raw length of "next" has not changed. */
            break;
        }
    }
    return zl;
}

 /*
 指针p指向前一个节点,len为当前长度
 如果前一个节点更改大小, 此函数返回编码前一节点长度所需的字节数的差异。
 如果需要更多的空间, 该函数返回正数;如果需要较少的空间, 则返回负数; 如果需要相同的空间, 则返回0。
 */
int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
    unsigned int prevlensize;
	
	//编码前一节点长度所需的字节数,存在prevlensize中
    ZIP_DECODE_PREVLENSIZE(p, prevlensize);
	//zipStorePrevEntryLength(NULL, len):获取编码len长度所需的字节数
    return zipStorePrevEntryLength(NULL, len) - prevlensize;
}

三、ziplist.h

//生成
unsigned char *ziplistNew(void);
//把 second 追加到 first, 合并这两个
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
//从头或者从尾插入s
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
//获取指定下标的
unsigned char *ziplistIndex(unsigned char *zl, int index);
//下一个节点
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
//前一个节点
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
//获取
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval);
//插入
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
//删除
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
//从index开始删除num个
unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num);
//比较
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
//查找
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
//长度
unsigned int ziplistLen(unsigned char *zl);
//原始长度
size_t ziplistBlobLen(unsigned char *zl);
//打印第一个
void ziplistRepr(unsigned char *zl);

quickList

  • ziplist需要连续内存,申请内存效率低
    限制ziplist长度和entry大小
  • ziplist如何存储大数据
    数据分片,多个ziplist
  • 管理多个ziplist
    使用quickList
/* quicklistNode is a 32 byte struct describing a listpack for a quicklist.
 * We use bit fields keep the quicklistNode at 32 bytes.
 * count: 16 bits, max 65536 (max lp bytes is 65k, so max count actually < 32k).
 * encoding: 2 bits, RAW=1, LZF=2.
 * container: 2 bits, PLAIN=1 (a single item as char array), PACKED=2 (listpack with multiple items).
 * recompress: 1 bit, bool, true if node is temporary decompressed for usage.
 * attempted_compress: 1 bit, boolean, used for verifying during testing.
 * extra: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *entry;
    size_t sz; //当前ziplist字节数
    unsigned int count : 16;//当前ziplist节点数
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* 数据容器类型 PLAIN==1 or PACKED==2 */
    unsigned int recompress : 1; //是否被解压
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int dont_compress : 1; /* prevent compression of entry that will be used later */
    unsigned int extra : 9; //预留字段
} quicklistNode;


/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
 * 'count' is the number of total entries.
 * 'len' is the number of quicklist nodes.
 * 'compress' is: 0 if compression disabled, otherwise it's the number
 *                of quicklistNodes to leave uncompressed at ends of quicklist.
 * 'fill' is the user-requested (or default) fill factor.
 * 'bookmarks are an optional feature that is used by realloc this struct,
 *      so that they don't consume memory when not used. */
typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;//所有ziplist的节点数量
    unsigned long len;//ziplist的数量
    signed int fill : QL_FILL_BITS;//ziplist的entry上限
    unsigned int compress : QL_COMP_BITS; //首尾不压缩的节点数量
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;
  • 中间节点压缩 节省内存

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

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

相关文章

陌生人社交软件如何破冰?

据艾媒咨询的数据显示&#xff0c;2020年中国移动社交用户规模已达9.24亿人&#xff0c;预计2022年中国移动社交用户整体突破10亿人。而早在2020年&#xff0c;我国陌生人社交用户规模已经达到了6.49亿人&#xff0c;虽然增速有所放缓&#xff0c;但整体规模还是较为庞大。 艾媒…

操作系统笔记——进程管理

操作系统笔记——进程管理2. 进程管理2.1 进程与线程2.1.1 进程的引入前趋图程序的顺序执行程序的并发执行2.1.2 进程的定义及描述进程的定义进程的特征进程和程序的关系进程与作业的区别进程的组成2.1.3 进程的状态与转换进程的5种基本状态进程的状态的相互转换2.1.4 进程的控…

java常见锁策略分享(包括cas和synchronized的优化)

前言 锁策略学习思维导图: 1.常见锁策略 ① 乐观锁和悲观锁 ● 它们是根据锁冲突的预测,如果预测锁冲突比较小,那就是乐观锁,反之,就是悲观锁. ● 举个例子:高考前夕,我总觉得高考题会很难,然后拼命做各种科目的题,全副武装的去应对高考,而我妈则觉得高考只是人生的一个阶段而…

PCB模块化设计04——USB-Type-C PCB布局布线设计规范

目录PCB模块化设计04——USB-Type-C PCB布局布线设计规范USB Type-C功能介绍信号图示Type-C接口引脚定义USB 2.0差分对电源和接地引脚RX和TX引脚CC1和CC2针脚VCONN引脚SBU1和SBU2针脚USB供电PCB设计布线要求PCB模块化设计04——USB-Type-C PCB布局布线设计规范 USB Type-C US…

STC的官网,是我永远忘不掉的炼丹炉

搞电子的&#xff0c;应该都搞过8051搞8051的&#xff0c;那应该都搞过STC在国内&#xff0c;STC已经成为了8051的代名词http://www.stcmcudata.com/如果你刚开始搞嵌入式&#xff0c;应该学单片机&#xff0c;你学习单片机&#xff0c;就应该学习下8051&#xff0c;学习8051&a…

Python+Pygame实现简单的单词小游戏

语言是一种艺术&#xff0c;但是作为语言的基础——词汇&#xff0c;却不像艺术那样赏心悦目。不断的记忆与复习&#xff0c;让词汇成为很多孩子在学习英语时&#xff0c;最难完全攻克的关卡。本文就来用Python制作一个简单的英语单词游戏吧 前言 语言是一种艺术&#xff0c;但…

【ArcGIS Pro二次开发】(17):打开GDB、SHP、CAD等各种数据

一、打开GDB数据库 // 输入一个数据库路径string gdbPath "C:\Users\Administrator\Documents\ArcGIS\Projects\Test\Test.gdb";await QueuedTask.Run(() >{// 如果文件夹存在并且包含有效的地理数据库&#xff0c;则会打开地理数据库。using (Geodatabase geoda…

【单片机/普中A2】学习笔记1-配置环境与STC-ISP烧录

目录前言连接到开发板micro-usb 测试安装串口驱动烧写准备源码烧录前言 目前我们的开发需求很简单&#xff0c;仅需三个软件&#xff1a; keli5 编写代码proteus8 professional 描绘电路板STC-ISP 串口烧录 具体教程在 CSDN 等博客平台上已经有很多&#xff0c;这里就不再赘述…

(排序2)希尔排序

写希尔排序注意&#xff1a; 写新元素融入有序数组的过程(end&tmp)将这个过程给多次类比到需要排序的一串数据中 (for&while)排完一组不够&#xff0c;需要排gap组 (再来for)敲定gap下标关系&#xff1a; 希尔排序与直接插入排序的区别与联系 希尔排序的话也叫做缩小…

刷题笔记【3】| 快速刷完67道剑指offer(Java版)

本文已收录于专栏&#x1f33b;《刷题笔记》文章目录前言&#x1f3a8; 1、斐波那契数列题目描述思路一&#xff08;递归&#xff09;思路二&#xff08;循环&#xff09;&#x1f3a8; 2、跳台阶题目描述思路一&#xff08;递归&#xff09;思路二&#xff08;循环&#xff09…

03-03 周五 镜像安装sshd和jupyter以及修改密码

03-03 周五 镜像安装sshd和jupyter以及修改密码时间版本修改人描述2023年3月3日15:34:49V0.1宋全恒新建文档 简介 由于在镜像中需要进行jupyter和sshd的安装&#xff0c;并且需要进行密码的修改&#xff0c;因此在该文档中记录了这两个交互方式的工程设计。 在线加密 在线加密…

Pycharm创建自定义代码片段

简介 PyCharm允许您创建自定义代码片段&#xff0c;也称为代码模板&#xff0c;以提高您的开发效率 实现步骤 1.添加代码模板 打开PyCharm并导航到File->Settings&#xff0c;或者按快捷键ctrl alt s 打开设置 ​ 按照如下序号步骤进行点击&#xff0c;点击“”按钮以…

基于canvas画布的实用类Fabric.js的使用Part.3

目录一、基于canvas画布的实用类Fabric.js的使用Part.1Fabric.js简介 开始方法事件canvas常用属性对象属性图层层级操作复制和粘贴二、基于canvas画布的实用类Fabric.js的使用Part.2锁定拖拽和缩放画布分组动画图像滤镜渐变右键菜单删除三、基于canvas画布的实用类Fabric.js的使…

gcc在Linux下如何运行一个C/C++程序

安装gcc&#xff1a;sudo apt-get install gcc&#xff08;之后输入密码即可&#xff09; 绝对路径的方式进入usr目录&#xff1a; cd /home /home/&#xff1a;是普通用户的主目录&#xff0c;在创建用户时&#xff0c;每个用户要有一个默认登录和保存自己数据的位置&#x…

【数据结构刷题集】链表经典习题

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;数据结构刷题集&#x1f50a;本专栏涉及到题目是数据结构专栏的补充与应用&#xff0c;只更新相关题目&#xff0c;旨在帮助提高代码熟练度&#x…

第14章_视图

第14章_视图 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公司…

Python 自动化指南(繁琐工作自动化)第二版:六、字符串操作

原文&#xff1a;https://automatetheboringstuff.com/2e/chapter6/ 文本是程序将处理的最常见的数据形式之一。您已经知道如何用操作符将两个字符串值连接在一起&#xff0c;但是您可以做得更多。您可以从字符串值中提取部分字符串&#xff0c;添加或删除空格&#xff0c;将字…

【新2023Q2模拟题JAVA】华为OD机试 - 找数字 or 找等值元素

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:找数字 or 找等值元素 题目 …

华为OD机试 用java实现 -【重组字符串】

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:重组字符串 题目 给定一个非…

计算机网络 第一章 概述小结

计算机网络 第一章 概述 1.1 因特网概述 名词解释&#xff1a;因特网服务提供者ISP&#xff08;Internet Service Provider&#xff09; 1.2 三种交换方式 电路交换&#xff1a; 优点&#xff1a;通信时延小、有序传输、没有冲突、适用范围广、实时性强、控制简单&#x…
最新文章