Redis数据结构及底层实现

Redis底层数据结构
1. SDS

Redis没有使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS),并将SDS用作Redis的默认字符串表示。

struct sdshdr {
    // 记录SDS所保存字符串的长度
    int len;
    // 记录buf数组中未使用字节的数量
    int free;
    // 字节数组,用于保存字符串
    char buf[];
}

SDS的特点:

  • 常数复杂度获取字符串长度: SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度仅为O(1)。
  • 杜绝缓冲区溢出: SDS API需要对SDS修改时,如果SDS的空间不满足要求,API会自动对SDS扩容,避免缓冲区溢出。
  • 减少修改字符串时带来的内存重分配次数: SDS通过空间预分配和惰性空间释放两种策略,减少修改字符串时的内存重分配次数。
  • 二进制安全: 不像C字符串只能保存文本数据,SDS可以保存像图片,音频,视频,压缩文件这样的二进制数据。
2.链表
typedef struct listNode {
    // 前置节点
    struct listNode *prev;
    // 后置节点
    struct listNode *next;
    // 节点的值
    void *value;
}

Redis的链表实现是双端列表,每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针。链表被广泛用于实现Redis的各种功能,比如列表键,发布与订阅,慢查询,监视器等。

3.map

Redis的字典使用哈希表作为底层实现。

typedef struct dictht {
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表已有节点数量
    unsigned long used;
}

typedef sturct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    }v;
    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;
}dictEntry;
  • 字典被广泛用于实现Redis的各种功能,包括数据库和哈希键。
  • 哈希表使用链地址法解决哈希冲突。
  • 在对哈希表进行扩容或者缩容时,rehash过程并不是一次性地完成的,程序渐渐式地将现有哈希表所包含的键值对rehash到新哈希表里面。
4.跳表
  • 跳表可以看成是有序列表加了索引。
  • 跳表是有序集合的底层实现之一。
5.整数集合

整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。

redis> SADD numbers 1 3 5 7 9
(integer) 5
redis> OBJECT  ENCODING numbers
"intset"
typedef struct intset {
    // 编码方式
    uint32_t encoding;
    // 集合包含的元素数量
    uint32_t length;
    // 保存元素的数组
    int8_t contents[];
} intset;
  • 整数集合是集合键的底层实现之一。
  • 整数集合的底层实现为数组,这个数组以有序,无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型。
  • 升级操作(扩容)为整数集合带来了操作上的便利性,并且尽可能地节省了内存;整数集合只支持升级,不支持降级。
6.压缩列表

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型内存数据结构。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
在这里插入图片描述

zlbytes记录整个压缩列表占用的内存字节数。

zltail记录压缩列表表尾节点距离压缩列表的起始地址有多少字节。

zllen记录压缩列表包含的节点数量。

  • 压缩列表是一种为节约内存而开发的顺序型数据结构。
  • 压缩列表被用作列表键和哈希键的底层实现之一。
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  • 添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作。

Redis数据类型实现

在这里插入图片描述

对于Redis保存的键值对来说,键总是一个字符串对象,而值可以是字符串对象,列表对象,哈希对象,集合对象和有序集合对象中的一种。

1.String
2.List

list可以由双向链表或者压缩列表实现。

redis> RPUSH numbers 1 "three" 5
(integer)3

压缩列表实现的list示意图:

zlbyteszltailzllen1“three”3zlend

当数据量比较小时,list会采用压缩列表实现;当数据量比较大时,list会采用双向链表实现。可以通过配置项修改临界值的大小。list-max-ziplist-valuelist-max-ziplist-entries

3.Hash

Hash可以由压缩列表或者哈希表实现。具体采用那种实现取决于数据的大小。小数据使用压缩列表。

用压缩列表实现Hash,当有新的键值对要加入Hash对象时,程序会先将保存了键的压缩列表节点推入到压缩列表尾部,然后再将保存了值的压缩列表节点推入到压缩列表表尾。

redis> HSET profile name "Tom"
(integer)1

redis> HSET profile age 25
(integer)1

压缩列表实现的Hash示意图:

zlbyteszltailzllen“name”“Tom”“age”25zlend
4.set

set可以由整数集合或者哈希表实现。

redis> SADD numbers 1 3 5
(integer)3
135

哈希表用作集合的底层实现时,哈希表的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而哈希表的值则全部被设置为NULL。

5.Sorted set|

有序列表可以使用压缩列表或者跳表实现。

使用压缩列表实现Sorted set时,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保存元素的分值(score)。

redis> ZADD price 8 apple 6.0 banana 7 cherry
(integer)3
zlbyteszltailzllen“banana”6.0“cherry”7“apple”8zlend

压缩列表实现Sorted set时,使用zset结构作为底层实现。

typedef struct zset {
    
    zskiplist *zsl;
    
    dict *dict;
} zset;

zset结构中的zsl跳跃表按分值大小保存了所有集合元素键,除此之外,zset结构中的dict字段为有序集合创建了一个从成员到分值的映射。通过哈希表,程序可以用O(1)的复杂度查找给定成员的分值。

为什么有序集合需要同时使用跳跃表和哈希表实现:因为性能。单个元素查找时哈希表更快,范围型操作跳跃表更快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值