redis基础数据结构和数据对象

本文介绍了Redis的基础数据结构,包括SDS、双链表、字典、跳跃表、整数集合和压缩列表。同时,详细阐述了这些数据结构在Redis中的应用,如字符串对象、列表对象、哈希对象、集合对象和有序集合对象的实现方式及其编码策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近学习看黄健宏先生写的《redis设计与实现》,开篇即介绍了redis的基础数据结构和数据对象,看了一遍后觉得还是有点没弄明白相互之间的关系,觉得很有必要整理一下自己的思绪。

1. redis基础数据结构

  • 简单动态字符串 -SDS
  • 双链表 -list
  • 字典 -dicht(hashtable)
  • 跳跃表 -skiplist
  • 整数集合 -intset
  • 压缩列表 -ziplist

2. redis基础数据对象

  • 字符串对象

    • raw类型
    • int 型
    • emstr型
  • 列表对象

    • ziplist
    • linkedlist
  • 哈希对象

    • ziplist
    • hashtable
  • 集合对象

    • intset
    • hashtable
  • 有序集合对象

    • ziplist
    • skiplist

3. 数据结构的详细说明

3.1 简单动态字符串 -SDS

简单动态字符串实际是对c语言char*的封装,redis实现于在sds.h中,类似于c++的string,以及stl的vector。是一种常用的封装。它是用如下的数据结构。

struct sdshdr {
    int len;     // buf 中已占用空间的长度
    int free;    // buf 中剩余可用空间的长度
    char buf[];   // 数据空间
};

3.2 双链表 -list

任何一本数据结构的书上都会有关于单链表的详细介绍,双链表就是在单链表的基础上增加了一个指向前驱的指针。redis实现于adlist.h

typedef struct listNode {
    struct listNode *prev; // 指向前一个节点
    struct listNode *next; // 指向后一个节点
    void *value;           // 节点的值
} listNode;

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;

3.3 字典 -dicht(hashtable)

3.3.1哈希表的数据结构
typedef struct dictEntry {    // 哈希表节点
    void *key;   // 键
    union {     // 值
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    struct dictEntry *next;    // 指向下个哈希表节点,形成链表redis使用链地址法处理哈希碰撞,
                               //故在每一个节点中都存在一个指向下一个节点的哈希表节点的指针。
} dictEntry;

typedef struct dictht {
    dictEntry **table;  // 哈希表数组
    unsigned long size;    // 哈希表大小
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;
    unsigned long used;    // 该哈希表已有节点的数量
} dictht;

redis使用Murmurhash2算法来计算键的哈希值。该算法于2008年发明,详细介绍查看该算法的官网。

3.3.2 字典的数据结构。
typedef struct dict {
    dictType *type; // 类型特定函数
    void *privdata;    // 私有数据
    dictht ht[2];   // 哈希表
    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */
} dict;

我们可以看到每个字典中包含了两个哈希表,一般情况下我们只是用ht[0],此时rehashidx为-1。当哈希表中元素增多,used接近于哈希表的大小时,需要对哈希表进行扩容。此时为rehash,redis使用一种渐进式rehash策略,即每次查询或者插入操作时,判断是否正在进行rehash,若正在进行,则转移部分ht[0]中的元素到ht[1]中,这样可以将转移操作分布到各次操作中,不会阻塞很长时间。当转移完成后,ht[1]赋值给ht[0],恢复rehashidx为-1;查找元素时,根据是否正在进行rehash,判断是否需要查询ht[2]。

3.4 跳跃表-skiplist

跳跃表是比较少见的一种数据结构,redis中用于两个地方,有序集合键和集群内部数据结构。下图是跳跃表的示例图(来自维基百科)。
跳跃表结构
网上也有很多跳跃表的说明,就不细说跳跃表了。

3.5 整数集合-intset

intset是集合键的底层实现之一,其数据结构定义在intset.h中。

typedef struct intset {
    uint32_t encoding;    // 编码方式
    uint32_t length;// 集合包含的元素数量
    int8_t contents[];    // 保存元素的数组

} intset;

其中encoding编码方式INTSET_ENC_INT16 ,INTSET_ENC_INT32,INTSET_ENC_INT64
从以上的数据结构定义可以很清楚的看出,不管使用哪种哪种编码,intset都是将数据序列化到contents中。当数据元素全是INTSET_ENC_INT16 类型时,可以使用较少的存储空间,但是当插入一个INTSET_ENC_INT32 类型是就需要升级了。

3.6 压缩集合-ziplist

压缩列表是列表键和哈希键的底层实现之一。在内存中开辟一块连续的存储单元。
非空 ziplist在内存中如下存储。第一排代表所占字节,第二排代表内存中数据。

442????2
zlbyteszltailzlenentry1entry2entryNzlend

4 对象与数据结构之间的实现关系

数据对象定义在redis.h中。type表示类型,encoding表示编码方式。

typedef struct redisObject {
    unsigned type:4;// 类型
    unsigned encoding:4; // 编码
    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;   // 引用计数
    void *ptr;// 指向实际值的指针
} robj;

4.1 字符串对象

  • 当字符串中保存的为int型整数时,内部使用int编码。
  • 当字符串中保存小于32字节字符串时使用emstr,emstr为只读,写时转换为raw。
  • 非以上两种情况时使用raw编码。

4.2 列表对象

4.3 哈希对象

4.4 集合对象

4.5 有序集合对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值