Redis存储原理与数据模型

Redis存储原理与数据模型

redis里默认有16个数据库。每个数据库是由字典组织起来的。

一 Redis存储结构

1.1 存储结构
Redis的存储结构分为两层,外层是由字典构成的,而值又有不同的数据结构,值的数据就属于内层存储结构。值分为字符串、链表、哈希表、集合和有序集合五个部分。

下面是Redis的整体的存储结构如下图所示:
在这里插入图片描述
值的不同的数据结构分别以如下所示的存储形式进行存储。

1.2 存储转换
在这里插入图片描述

字典实现:
redis DB KV组织是通过字典来实现的;hash结构当节点超过 512 个或者单个字符串长度大于64时,hash结构采用字典实现。
实现源码如下:

typedef struct dict {
    dictEntry **table;  //指针数组
    dictType *type;     //
    unsigned long size; //数组大小
    unsigned long sizemask; //数组大小减一 
    unsigned long used; //hashtable中元素的个数
    void *privdata;
} dict;

hash数组的大小一般被设置为2的n次幂,因为这样可以进行算法的优化。redis的hash(key)后会生成64位的整数n,而元素在hash表中的位置的计算方式可以被优化为:n % size = n & (size - 1)。位运算远快于取余运算。

1.3 hash冲突
redis用的方法是拉链法,解决的哈希冲突。将冲突的元素按照先来后到的顺序,在数组上用链表链接起来。

负载因子
负载因子 = used / size ; used 是数组存储元素的个数, size 是数组的长度。它可以描述hash冲突的激烈程度,负载因子越小,冲突越小;负载因子越大,冲突越大;

扩容
如果负载因子 > 1 ,则会发生扩容;扩容的规则是翻倍;
如果正在 fork (在rdb、aof复写以及rdb-aof混用情况下)时,会阻止扩容;但是此时若负载因
子 > 5 ,索引效率大大降低, 则马上扩容;这里涉及到写时复制原理;

写时复制
写时复制核心思想:只有在不得不复制数据内容时才去复制数据内容。它是Linux内核的功能。
redis进程在fork一个进程后,两个进程的虚拟内存指向同一块物理内存。当父进程要修改内存时,才会发生子进程复制一块完整的父进程的物理内存的事情。

扩容的条件是什么
缩容的条件是什么
渐进式:

缩容
如果负载因子 < 0.1 ,则会发生缩容;缩容的规则是恰好包含 used 的 。
恰好的理解:假如此时数组存储元素个数为 9,恰好包含该元素的就是 ,也就是 16。

渐进式rehash
当 hashtable 中的元素过多的时候,不能一次性 rehash 到 ht[1] ;这样会长期占用 redis ,其他命令得不到响应;所以需要使用渐进式 rehash 。

rehash步骤:
将 ht[0] 中的元素重新经过hash函数生成64位整数,再对 ht[1] 长度进行取余,从而映射到ht[1] ;
渐进式规则:

  1. 分治的思想,将 rehash 分到之后的每步增删改查的操作当中;
  2. 在定时器中,最大执行一毫秒 rehash ;每次步长 100 个数组槽位;

注意事项:处于渐进式rehash阶段时,是否会发生扩容缩容?不会!

Scan
Scan 是渐进式的 Keys。
scan cursor [MATCH pattern] [COUNT 1 count] [TYPE type]

怎么避免遍历数据的时候,不重复不遗漏?
在这里插入图片描述
采用高位进位加法的遍历顺序, rehash 后的槽位在遍历顺序上是相邻的。
遍历目标是:不重复,不遗漏 ;
会出现一种重复的情况:在scan的过程中,发生两次缩容的时候,会发生数据重复。

expire机制

# 只支持对最外层key过期;
expire key seconds
pexpire key milliseconds
ttl key
pttl key

大KEY
在 redis 实例中形成了很大的对象,比如一个很大的 hash 或很大的 zset,这样的对象在扩容的时候,会一次性申请更大的一块内存,这会导致卡顿;如果这个大 key 被删除,内存会一次性回收,卡顿现象会再次产生;

如果观察到 redis 的内存大起大落,极有可能因为大 key 导致的;

二 跳表

跳表是一种空间换时间的数据结构。它是可以实现二分查找的有序链表。

理想跳表
在这里插入图片描述
每隔一个节点生成一个层级节点;模拟二叉树结构,以此达到搜索时间复杂度为o(log2^n)。

但是如果对理想跳表结构进行删除增加操作,很有可能改变跳表结构;如果重构理想结构,将是巨大的运算;考虑用概率的方法来进行优化;从每一个节点出发,每增加一个节点都有1/2的概率增加一个层级,1/4的概率增加两个层级,1/8的概率增加3个层级,以此类推。经过证明,当数据量足够大(256)时,通过概率构造的跳表趋向于理想跳表,并且此时如果删除节点,无需重构跳表结构,此时依然趋向于理想跳表;此时时间复杂度为(1-1/nc)O(log2n)。

redis跳表
从节约内存出发,redis 考虑牺牲一点时间复杂度让跳表结构更加变扁平,就像二叉堆改成四叉堆结构;并且redis 还限制了跳表的最高层级为 32 ;节点数量大于 128 或者有一个字符串长度大于 64 ,则使用跳表( skiplist );

redis源码中跳表的数据结构:

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span; //相对于上一层的跨度
    } level[]; //柔性数据是啥?
} zskiplistNode;

typedef struct zskiplist {//跳表的数据结构
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;//跳表有多少层
} zskiplist;

三 redis io多线程

在这里插入图片描述
线程池总结
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值