redis 底层数据结构详解

这篇具有很好参考价值的文章主要介绍了redis 底层数据结构详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

 

1.字符串

1.1 SDS定义

1.2 SDS1好处

2.列表

2.1 void 实现多态

3 字典

3.1   底层实现是hash表

3.2 字典结构

3.3 哈希算法

3.3.1 rehash

3.3.2 rehash的触发时机

3.3.3 渐进式rehash

扩展或收缩哈希表需要将ht[0]里面的所有键值对rehash到ht[1]里面,但是,这个rehash动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。

4.跳跃表

4.1 跳跃表-zskiplistNode 

4.2 跳跃表 - zskiplist

5.压缩列表


1.字符串

  • Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型(对象),并将SDS用作Redis的默认字符串表示。
  • 在Redis里面,C字符串只会作为字符串字面量(string literal)用在一些无须对字符串值进行修改的地方,比如打印日志,当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用SDS来表示字符串值。
  • 除了用来保存数据库中的字符串值之外,SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的

1.1 SDS定义

struct sdshdr {
    //SDS所保存字符串的长度
    int len;

    // 记录buf数组中未使用字节的数量
    int free;

    // 字节数组,用于保存字符串
  char buf[];
};

1.2 SDS1好处

  1. C 语言的字符串如果想要得到他的长度,需要进行遍历,意味着时间复杂度为 o(N)。如果使用sds,我们的长度直接从len属性里获取,o(1). 本质上,其实就是多了一个len属性,保存了我们的字符串的长度;
  2. C语言的字符串如果进行我们的 扩展(增加字符串的长度) 或者 缩减(减少字符串的长度)。 进行扩展:我们必须要提前分配内存空间,一旦忘了分配,造成缓冲区溢出;进行缩减:必须要有意识的进行空间的释放,否则造成空间浪费。无论是进行扩展还是缩减,都需要进行内存的重新分配,耗时啊。 SDS 来说,他不会造成缓冲区溢出的问题,是封装好的对象,他已经为我们考虑了这部分内存的扩展及缩减问题。
  3. 二进制安全问题。C 语言来说,他的字符串是二进制不安全的,因为C语言的 空字符 结尾的设计,如果一个字符串中间有空字符串,那么 c语言的字符串的二进制转化会遗弃第一个空字符出现的后边的所有内容。 举例: m \0 s g \0.  如果是 C语言进行二进制转化,只对 m 进行转化; SDS 就不是啦,我们是自己封装的对象,我们能支持二进制的安全性,我能全部进行转化。

2.列表

列表键的底层实现就是一个链表,链表中的每个节点都保存了一个数值

typedef struct list {
    // 表头节点
    listNode * head;
    // 表尾节点
    listNode * tail;
    // 链表所包含的节点数量
    unsigned long len;
    // 节点值复制函数
    void *(*dup)(void *ptr);
    // 节点值释放函数
    void (*free)(void *ptr);
    // 节点值对比函数
    int (*match)(void *ptr,void *key);
} list;

typedef struct listNode {
    // 前置节点
    struct listNode * prev;
    // 后置节点
    struct listNode * next;
    // 节点的值
    void * value;
}listNode;

2.1 void 实现多态

  •  void 这里代表的是 多态。     如果你在java里想复制一个值,那么你是不是要么 知道这个值的类型,要么你使用 object 。 对于 redis 来说,由于 list 里可以存放各种类型的数值,那么,如果你要进行多种类型值的一些统一操作的话,需要使用 void 的返回值类型,这里体现了 我们 多态的一个性质。

3 字典

3.1   底层实现是hash表

 Redis的字典使用哈希表作为底层实现,一个哈希表(dictht)里面可以有多个哈希表节点(dictEntry ),而每个哈希表节点(dictEntry )就保存了字典中的一个键值对。

Redis字典所使用的哈希表由dict.h/dictht结构定义:

Redis字典所使用的哈希表由dict.h/dictht结构定义:
typedef struct dictht {
    // 哈希表节点组成的数组
    dictEntry **table; // 
    // 哈希表大小
    unsigned long size;
    //哈希表大小掩码,用于计算索引值 总是等于size-1
    unsigned long sizemask;
    // 该哈希表已有节点的数量(非null节点)
    unsigned long used;
} dictht;


哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对:
typedef struct dictEntry {
    // 键
    void *key;
    // 值
    union{
        void *val;
        uint64_tu64;
        int64_ts64;
    } v;
    // 指向下个哈希表节点,形成链表 (hash 冲突,链地址法)
    struct dictEntry *next;
} dictEntry;

3.2 字典结构

字典,是基于 hash 表的结构上,再次进行的一层封装

Redis中的字典由dict.h/dict结构表示:  
typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privdata;
    // 哈希表数组,包含两个 dictht
    dictht ht[2]; 
    // rehash索引当rehash不在进行时,值为-1
    int rehashidx; 
} dict;


type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
·而privdata属性则保存了需要传给那些类型特定函数的可选参数。
typedef struct dictType {
    // 计算哈希值的函数
    unsigned int (*hashFunction)(const void *key);
    // 复制键的函数
    void *(*keyDup)(void *privdata, const void *key);
    // 复制值的函数
    void *(*valDup)(void *privdata, const void *obj);
    // 对比键的函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    // 销毁键的函数
    void (*keyDestructor)(void *privdata, void *key);
  • ht属性是一个包含两个哈希表的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
  • 除了ht[1]之外,另一个和rehash有关的属性就是rehashidx,它记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1。

3.3 哈希算法

当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面

  1. 计算哈希值的函数 :int hashValue = unsigned int (*hashFunction)(const void *key);
  2. 获取最终的hash 值 : hashValue &  ht[x].sizemask。

3.3.1 rehash

随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。 扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成,Redis对字典的哈希表执行rehash的步骤如下:

  1. 为字典的dict.ht[1]哈希表分配空间 ·如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2 n(2的n次方幂);       ht0 中 used = 10;为ht1分配的大小是 10*2 = 20?2的4次方是 16, 2的 5次方是 32. 32 是第一个大于 20的并且是 2 的 n次幂,所以 ht1的空间分配大小为 32。 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2 n。      ht0 中 used = 10,那么 2的 4次方是 16, 16是第一个大于10的并且未 2的 n次幂的数字,所以此时 ht1的空间分配大小为 16.
  2. 将保存在ht[0]中的所有键值对rehash到ht[1]上面。 dictTyppe里边的第一个函数结果 & sizemask (如果是扩展,sizemask = 31; 如果是收缩,sizemask = 15)
  3. 当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

3.3.2 rehash的触发时机

当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:

  1. 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。 比如,我的hash表 size是 8, 现在有10个used(有hash冲突,肯定有链表结构),此时负载因子是 10 /8=1.25 > 1 。并且没有bgsvae和 reWaof,此时触发 扩展 rehash。
  2. 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。 比如 我的hash表 size是 8, 现在有10个used(有hash冲突,肯定有链表结构),此时负载因子是 10 /8=1.25 > 1 ,这时候,由于有 bg和 bgre,所以不能触发。 但是随着时间的推移,我的 used 变成了 80, 此时负载因子是 80/8=10. 及时现在有bg和rebg,还是可以触发 rehash

3.3.3 渐进式rehash

  • 扩展或收缩哈希表需要将ht[0]里面的所有键值对rehash到ht[1]里面,但是,这个rehash动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。

  • 这样做的原因在于,如果哈希表里保存的键值对数量不是三四个,而是四百万、四千万甚至四亿个键值对,那么要一次性将这些键值对全部rehash到ht[1]的话,庞大的计算量可能会导致服务器在一段时间内停止服务。因此,为了避免rehash对服务器性能造成影响,服务器不是一次性将ht[0]里面的所有键值对全部rehash到ht[1],而是分多次、渐进式地将ht[0]里面的键值对慢慢地rehash到ht[1]。

哈希表渐进式rehash的详细步骤:

  1. 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。
  2. 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始。 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性的值增一。每迁移一个 keyvalue,就需要在 rehashidx 上自增 1. 如果有10万个值,我们 rehashidx会从0自增到 10万结束。
  3. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成。包括之前所说的 ,ht1成为了 ht0,再重新创建一个空的 ht1备用。

4.跳跃表

  • 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
  • Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。 和链表、字典等数据结构被广泛地应用在Redis内部不同,Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构,除此之外,跳跃表在Redis里面没有其他用途。
  • Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义,其中zskiplistNode结构用于表示跳跃表节点,而zskiplist结构则用于保存跳跃表节点的相关信息。

4.1 跳跃表-zskiplistNode 

  • 跳跃表节点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快。每次创建一个新跳跃表节点的时候,随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”
  • 每个层都有一个指向表尾方向的前进指针(level[i].forward属性),用于从表头向表尾方向访问节点。
  • 节点的后退指针(backward属性)用于从表尾向表头方向访问节点:跟可以一次跳过多个节点的前进指针不同,因为每个节点只有一个后退指针,所以每次只能后退至前一个节点。
typedef struct zskiplistNode {
    // 层
    struct zskiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];

    // 后退指针
    struct zskiplistNode *backward;

    // 分值(例:你的账号排名)
    double score;

    // 成员对象 (例: 你的游戏账号)
    robj *obj;
} zskiplistNode;

4.2 跳跃表 - zskiplist

  • header和tail指针分别指向跳跃表的表头和表尾节点,通过这两个指针,程序定位表头节点和表尾节点的复杂度为O(1)。
  • 通过使用length属性来记录节点的数量,程序可以在O(1)复杂度内返回跳跃表的长度。 level属性则用于在O(1)复杂度内获取跳跃表中层高最大的那个节点的层数量
typedef struct zskiplist {
    // 
表头节点和表尾节点
    structz skiplistNode *header, *tail;
    // 
表中节点的数量
    unsigned long length;
    // 
表中层数最大的节点的层数
    int level;
} zskiplist;

5.压缩列表

  • 压缩列表(ziplist)是列表和哈希的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
  • 压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值

redis 底层数据结构详解文章来源地址https://www.toymoban.com/news/detail-405521.html

到了这里,关于redis 底层数据结构详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Redis - 底层数据结构

    Redis - 底层数据结构

    Redis 的底层数据结构主要以下几种: SDS(Simple Dynamic String, 简单动态字符串) ZipList(压缩列表) QuickList(快表) Dict(字典) IntSet(整数集合) ZSkipList(跳跃表) 在 Redis 中,并不会直接使用 C 语言自带的字符串结构作为实际的存储结构,而只是将字符串作为字面量使用,大多数情况使用自

    2023年04月12日
    浏览(12)
  • Redis五种数据结构底层编码结构

    Redis五种数据结构底层编码结构

    Redis中的 任意数据类型的键和值都会被封装为一个RedisObject ,也叫做Redis对象,源码如下: 对象头不包含数据就已经占16字节,如果数据存string型,一个string一个对象头比较浪费空间,存大量数据时还是建议使用集合,这样可以共用一个对象头更加节省空间 Redis中会根据存储

    2024年02月11日
    浏览(9)
  • Redis - 数据类型映射底层结构

    Redis - 数据类型映射底层结构

    从数据类型上体现就是,同一个数据类型,在不同的情况下会使用不同的编码类型,底层所使用的的数据结构也不相同。 字符串对象的编码可以是 int 、 raw 和 embstr 三者之一。 embstr 编码是专门用于保存简短字符串的一种优化编码方式,与 raw 编码会调用两次内存分配函数分

    2023年04月21日
    浏览(14)
  • redis的hash数据结构底层简记

    hash:k和v都是string的hash表。 HSET(设置集合数据,4.0之前只能设置1个,之后可以设置多个),HSETNX(若k不存在则设置对应v),HDEL(删除指定kv,可以一次删除多个),DEL(删除Hash对象),HMSET(设置多个kv,4.0之后废弃),HGETALL(查找全部数据),HGET(查询k对应的v),HLEN(查

    2024年02月21日
    浏览(14)
  • 【redis】redis的5种数据结构及其底层实现原理

    【redis】redis的5种数据结构及其底层实现原理

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无序集合)及zset(有序集合)。 在秒杀项目里,我用过redis的Set和Hash结构: String:一个 key 对应一个字符串,string是Redis 最基本的数据类型。(字节的abase框架只实现了redis的string数据结构,导致我们如

    2024年02月09日
    浏览(13)
  • 【从零开始学习Redis | 第八篇】认识Redis底层数据结构(下)

    【从零开始学习Redis | 第八篇】认识Redis底层数据结构(下)

    目录 前言:   ZipList: Ziplist的特性: QucikList: QuicList特征: SkipList: 跳表特征: RedisObijct:  小心得: 总结:           在现代软件开发中,数据存储和处理是至关重要的一环。为了高效地管理数据,并实现快速的读写操作,各种数据库技术应运而生。其中,Redis作为一种

    2024年04月12日
    浏览(16)
  • Redis追本溯源(二)数据结构:String、List、Hash、Set、Zset底层数据结构原理

    Redis追本溯源(二)数据结构:String、List、Hash、Set、Zset底层数据结构原理

    Redis 并没有直接用 C 语言的字符串,而是自己搞了一个 sds 的结构体来表示字符串,这个 sds 的全称是 Simple Dynamic String,翻译过来就是“简单的动态字符串”。 安全的二进制存储 资源。关于sds的扩容和缩容下面会进行详细的介绍,这里先不赘述了。 在一些情况中,我们需要

    2024年02月16日
    浏览(12)
  • Redis从入门到精通【高阶篇】之底层数据结构跳表(SkipList)

    Redis从入门到精通【高阶篇】之底层数据结构跳表(SkipList)

    上个篇章回顾,我们上个章节我们学习了《Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解》,我们从源码层了解整数集由一个头部和多个数据块组成。头部中存储了整数集的元素个数、编码方式和数据块的起始地址等信息。数据块中存储了实际的整型数据,当

    2024年02月09日
    浏览(13)
  • 【Redis】关于Redis数据结构简单动态字符串(SDS)的一些杂记

    推荐几篇关于SDS数据结构讲解较为详细的文章: 一、简单动态字符串 — Redis 设计与实现 (redisbook.readthedocs.io) 二、深入理解Redis之简单动态字符串 - itbsl - 博客园 (cnblogs.com) 三、Redis内部数据结构详解(2)——sds - 铁蕾的个人博客 (zhangtielei.com) 四、简单动态字符串 — Redis 设计与

    2023年04月14日
    浏览(12)
  • Redis数据结构与对象-字符串对象SDS

    Redis没有使用C的字符串,而是自己构建了简单动态字符串(Simple Dynamic String),简称SDS。通过这种字符串格式能够对redis字符串操作进行提速。下面介绍原理。 sds数据格式如下: 比如,一个sds 中存的是 “Redis” ,那么buf 中是一个char型的数组,存5个字符R, e,d,i,s len =5;free

    2023年04月16日
    浏览(17)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包