Redis关键数据结构深度剖析

深度剖析Redis关键数据结构

动态字符串SDS

字符串是Redis中常用的数据结构之一。

C语言字符串的局限

Redis未直接采用C语言原生字符串,原因在于C语言字符串存在以下弊端:

  • 长度获取:需线性时间复杂度(O(n))遍历数组来获取长度。
  • 非二进制安全:以\0作为结束标识,致使字符串无法容纳含\0的二进制数据,像图片、音频等无法存储。
  • 操作不便:不可直接修改,进行拼接等操作时需手动处理内存,存在风险。

SDS详解

Redis构建了简单动态字符串(SDS)来替代C语言字符串。

SDS结构

Redis用C语言实现,SDS是结构体,源码示例:

struct __attribute__ ((packed)) sdshdr8 {  
    uint8_t len; /* buf中已存字符串字节数,不包含结束标记 */  
    uint8_t alloc; /* buf申请的总字节数,不包含结束标记 */  
    unsigned char flags; /* 用于区分不同SDS头类型,控制头大小 */  
    char buf [];  
};

为适配不同存储需求,还有sdshdr16sdshdr32sdshdr64等,数字对应int的位长,sdshdr5已弃用。各成员变量作用如下:

成员变量 描述
len 记录字符串长度
alloc 分配给字符数组的空间长度
flags 标识SDS类型
buf[] 字节数组,存储实际数据

例如,存储字符串“name”的SDS结构示意:

SDS结构示例.png

动态扩容

SDS具动态扩容能力。通过alloc-len计算剩余空间,判断缓冲区是否能满足操作。空间不足时,Redis会自动扩展。扩容规则:若所需长度小于1MB,按翻倍扩容;超过1MB则进行内存预分配,新增长度为原长度加1MB,减少频繁内存分配,优化性能。示例代码:

hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) 
{ 
    ...
    // 若剩余空间足够,直接返回
    if (avail >= addlen) return s; 
    // 获取当前长度
    len = hi_sdslen(s); 
    sh = (char *)s - hi_sdsHdrSize(oldtype); 
    // 计算扩展后至少需的长度
    newlen = (len + addlen); 
    // 根据新长度分配空间
    if (newlen < HI_SDS_MAX_PREALLOC)
        newlen *= 2; 
    else
        newlen += HI_SDS_MAX_PREALLOC; 
        ... 
}

SDS解决了C字符串的问题:获取长度通过len实现(O(1))时间复杂度;二进制安全靠遍历len个字符;支持动态扩容,减少内存分配次数。

IntSet

IntSet是Redis中set集合的一种实现,基于整数数组,具长度可变、有序等特性。

结构

typedef struct intset {
    uint32_t encoding;/* 编码方式,支持16位、32位、64位整数 */
    uint32_t length;/* 元素个数 */
    int8_t contents[];/* 整数数组,存储集合数据 */
} intset;

其中encoding有三种模式,对应不同整数大小:

/* 编码有序,INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64 */
#define INTSET_ENC_INT16(sizeof(int16_t))/* 2字节整数,类似Java的short */
#define INTSET_ENC_INT32(sizeof(int32_t))/*4字节整数,类似Java的int */
#define INTSET_ENC_INT64(sizeof(int64_t))/* 8字节整数,类似Java的long */

元素按升序存储在contents数组,结构示例:

IntSet结构.png

IntSet升级

以元素为[5,10,20](编码为INTSET_ENC_INT16)为例,添加50000时会升级编码。流程:

  1. 升级编码为INTSET_ENC_INT32,扩容数组,计算所需空间。
  2. 倒序拷贝原数组元素到新位置,避免覆盖。
  3. 插入新元素到数组末尾。
  4. 更新编码和长度属性。

部分源码示例:

intset *intsetAdd(intset *is,int64_t value,uint8_t *success) {
    uint8_t valenc = _intsetValueEncoding(value); // 获取值编码
    uint32_t pos; // 插入位置
    if(success) *success=1;
    // 若编码超出当前IntSet编码,升级
    if(valenc >intrev32ifbe(is->encoding)) {
        return intsetUpgradeAndAdd(is,value);
    } else {
        // 查找元素位置
        if(intsetSearch(is,value,&pos)) {
            if(success) *success =0; // 找到则不插入
            return is;
        }
        // 扩容数组
        is = intsetResize(is,intrev32ifbe(is->length)+1);
        // 移动元素腾出空间
        if(pos <intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }
    // 插入新元素
    intsetSet(is,pos,value);
    // 更新元素长度
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    uint8_t curenc = intrev32ifbe(is->encoding);
    uint8_t newenc = _intsetValueEncoding(value);
    int length = intrev32ifbe(is->length);
    int prepend = value < 0 ? 1 : 0;
    // 更新编码
    is->encoding = intrev32ifbe(newenc);
    // 扩容数组
    is = intsetResize(is,intrev32ifbe(is->length)+1);

    /* 倒序拷贝元素 */
    while(length--)
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    /* 插入新元素 */
    if (prepend)
        _intsetSet(is,0,value);
    else
        _intsetSet(is,intrev32ifbe(is->length),value);
    // 更新长度
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

IntSet特点:元素唯一有序、支持类型升级、底层用二分查找。

Dict

Redis键值映射靠Dict实现。

结构

Dict由哈希表(dictht)、哈希节点(dictEntry)、字典(dict)组成。

typedef struct dictht {
    dictEntry **table; // entry数组,元素是指向entry的指针
    unsigned long size; // 哈希表大小(2的幂)
    unsigned long sizemask; // size-1,用于计算索引
    unsigned long used; // entry个数
} dictht;

typedef struct dictEntry {
    void *key; // 键
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v; // 值
    struct dictEntry *next; // 下一个entry指针(解决哈希冲突)
} dictEntry;

typedef struct dict {
    dictType *type; // dict类型,含哈希函数
    void *privdata; // 私有数据,用于特殊哈希运算
    dictht ht[2]; // 两个哈希表,ht[0]存数据,ht[1]用于rehash
    long rehashidx; // rehash进度,-1表示未rehash
    int16_t pauserehash; // rehash暂停标志
} dict;

结构示例:

Dict结构示例.png

添加元素

添加键值对时,先计算key的hash值,通过h & sizemask确定存储索引,哈希冲突用头插法解决。

Dict扩容与收缩

Dict根据负载因子(LoadFactor=used/size)触发扩容或收缩。负载因子≥1且无后台进程时扩容;负载因子<0.1时收缩。扩容大小为第一个≥used+1的2的幂,收缩大小为第一个≥used的2的幂(不小于4)。

渐进式rehash

rehash分多次完成,每次操作时将ht[0]的entry链表rehash到ht[1],最终切换哈希表,确保ht[0]只减不增。

ZipList

ZipList是特殊的连续内存块结构,可双端操作,时间复杂度(O(1))。

结构

属性 类型 长度 用途
zlbytes uint32_t 4字节 记录压缩列表占用内存字节数
zltail uint32_t 4字节 表尾节点距起始地址偏移量
zllen uint16_t 2字节 节点数量,最大值65534
entry 列表节点 不定 存储的节点数据
zlend uint8_t 1字节 标记压缩列表末端

ZipListEntry

节点含前一节点长度、编码、内容。前一节点长度占1或5字节(<254用1字节,≥254用5字节);编码分字符串和整数,字符串以“00”“01”“10”开头,整数以“11”开头。

连锁更新问题

当连续节点长度在250 - 253字节间,插入长度为254字节的数据时,会引发后续节点的连续空间扩展,即连锁更新。

QuickList

为解决ZipList连续内存申请问题,Redis引入QuickList,是节点为ZipList的双端链表。

结构

typedef struct quicklist {
    quicklistNode *head; // 头节点指针
    quicklistNode *tail; // 尾节点指针
    unsigned long count; // 所有ziplist的entry总数
    unsigned long len; // ziplists总数
    int fill : QL_FILL_BITS; // ziplist的entry上限
    unsigned int compress : QL_COMP_BITS; // 首尾不压缩节点数
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

typedef struct quicklistNode {
    quicklistNode *prev; // 前一节点指针
    quicklistNode *next; // 下一节点指针
    unsigned char *zl; // 当前节点的ZipList指针
    unsigned int sz; // 当前节点ZipList字节大小
    unsigned int count : 16; // 当前节点ZipList entry个数
    unsigned int encoding : 2; // 编码方式(ZipList或lzf压缩)
    unsigned int container : 2; // 数据容器类型
    unsigned int recompress : 1; // 是否解压缩
    unsigned int attempted_compress : 1; 
    unsigned int extra : 10; /* 预留字段 */
} quicklistNode;

配置

通过list-max-ziplist-size控制ZipList entry数量,通过list-compress-depth控制节点压缩,首尾不压缩。

SkipList

SkipList是有序链表,节点含分数和值,有多级指针提高查询效率。

结构

typedef struct zskiplist {
    struct zskiplistNode *header, *tail; // 头尾节点指针
    unsigned long length; // 节点数量
    int level; // 最大索引层级
} zskiplist;

typedef struct zskiplistNode {
    sds ele; // 节点值
    double score; // 节点分数,用于排序
    struct zskiplistNode *backward; // 前一节点指针
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 下一节点指针
        unsigned long span; // 索引跨度
    } level[]; // 多级索引数组
} zskiplistNode;

RedisObject

Redis数据类型的键值封装为RedisObject,含16字节头部。

编码方式

Redis有11种编码方式,常见:

编号 编码方式 说明
0 OBJ_ENCODING_RAW raw编码动态字符串
1 OBJ_ENCODING_INT long类型整数的字符串
2 OBJ_ENCODING_HT hash表(字典dict)
5 OBJ_ENCODING_ZIPLIST 压缩列表
6 OBJ_ENCODING_INTSET 整数集合
7 OBJ_ENCODING_SKIPLIST 跳表
8 OBJ_ENCODING_EMBSTR embstr的动态字符串
9 OBJ_ENCODING_QUICKLIST 快速列表

五种数据类型的编码

  • String:分INT、EMBSTR、RAW编码,根据长度和内容选择。
  • List:3.2前用ZipList和LinkedList,3.2后用QuickList。
  • Set:分INTSET和HT编码,元素为整数且数量少时用INTSET,否则用HT。
  • Zset:分ZipList、HT、SkipList编码,元素少时用ZipList,多时用HT+SkipList。
  • Hash:分ZipList和HT编码,元素少时用ZipList,多时用HT。

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/12587.html

(0)
LomuLomu
上一篇 2025 年 6 月 19 日
下一篇 2025 年 6 月 19 日

相关推荐

  • 开启AI大模型应用开发新篇:LangChain打造RAG增强检索生成

    开启AI大模型应用开发的新篇章:LangChain助力RAG增强检索生成 检索增强生成(RAG)是一种将“向量检索”和“大语言模型”相结合的技术途径,能在问答、摘要、文档分析等场景中极大提升准确性与上下文的利用率。 本文将基于 LangChain 构建完整的 RAG 流程,结合 PGVector 作为向量数据库,并用 LangGraph 构建状态图来控制流程…

    2025 年 6 月 23 日
    8100
  • Java项目构建:掌握Maven仓库的高效配置技巧

    Java项目构建:掌握Maven仓库的高效配置技巧 核心术语:Maven构建系统、资源库管理、组件依赖、项目构建工具、Java编程、优化方案、项目对象模型内容概述:本指南将系统讲解Maven资源库在Java项目构建中的高效配置方法。我们将从Maven资源库的基础架构开始,全面剖析本地资源库与云端资源库的运作机制,阐释依赖管理的核心原理,并配合具体示例演示如何…

    未分类 2025 年 5 月 13 日
    10400
  • Git 实战秘籍:从萌新到高手全解析

    文章标题: Git 实战秘籍:从新手到高手全解析 文章内容: Git属于当前十分流行的分布式版本控制系统,在软件开发里被广泛运用。本文将会全面介绍Git的各类功能以及使用办法,其中包含大量的代码示例和实践方面的建议。 文章目录 Git基础概念 版本控制系统 Git的特点 Git的三个区域 Git文件状态 Git安装与配置 安装Git Linux macOS …

    2025 年 7 月 9 日
    6300
  • 深入解析JSP技术:从基础到实战应用

    JSP(JavaServer Pages)作为Java EE体系中的核心组件,为动态网页开发提供了高效解决方案。其设计初衷在于简化服务端编程,特别适用于需要动态生成HTML、XML等内容的Web项目开发。 一、JSP技术概览 技术定义: JSP是基于Java语言的动态网页技术标准 采用HTML嵌入Java代码的模式,服务端执行后返回处理结果 运行机制: 用户…

    未分类 2025 年 5 月 19 日
    15000
  • 🚀 2025年最新IDEA激活码分享 | 永久破解IDEA终极教程(附破解补丁)

    💻 适用Jetbrains全家桶(IDEA/PyCharm/DataGrip等) 先给大家看看最新IDEA版本破解成功的实锤截图!🎉 有效期直接拉到2099年,简直不要太爽! 下面我就手把手教大家如何激活IDEA到2099年,这个方法同样适用于旧版本哦!✨ 无论你是Windows、Mac还是Linux系统,什么版本都能搞定! 📥 下载IDEA安装包 如果已经…

    2025 年 5 月 13 日
    25400

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信