Redis底层核心数据结构与Redis6.0新特性

Redis底层核心数据结构

Redis是key-value数据结构,k-v 在java中会想到map,redis中是叫dict。

redis还是数据库,它是使用数组和链表来存储海量的数据的。

hash(key) --> 自然数 % array.size --> 这样就得到了数组的下标了,然后把key-value保存在数组的这个位置

如果产生了hash冲突,是使用链表来解决的,使用的头插法



DB

Redis中默认有16个数据库,底层是redisDb对象,代码如下

typedef struct redisDb {
   
    dict *dict;       // 上面提到的 key-value  就是存储在dict中的           
    dict *expires;    // 存储各个key的过期时间      
    dict *blocking_keys;     // 存储的是阻塞队列相关的内容     
    dict *ready_keys;        // 维护key和client客户端连接之间的对应关系
    dict *watched_keys;      // 关于事务处理是存放在watched_keys
    int id;           // 这个就是数据库的id  0~15              
    long long avg_ttl;           
    unsigned long expires_cursor;  
    list *defrag_later;          
} redisDb;

重点是要讲解dict,我们可以理解为这就是一个hashtable

typedef struct dict {
   
    dictType *type;		// 指定hash算法,并且产生hash冲突后去进行equals比较   是否进行覆盖或者头插法插入元素
    void *privdata;
    dictht ht[2];	// 这就是一个hashtable结构,ht[0]是老数组  ht[1]是新数组,指向下面的dictht对象
    long rehashidx; 
    unsigned long iterators;  
} dict;

dictht的代码如下所示。这就是一个hashtable的数据结构,每个dict字典都有两个dictht,目的就是实现一个渐进式的rehash,其实就是数组的扩容,把老数组的内容拷贝到扩容后新数组中去。Redis的扩容是newSize=oldSize*2,扩容完成后并不是一次性把所有的key-value移动到新数组中去,而是一次移动一部分数据,然后去处理用户请求,过一会了又移动一部分,扩容是在master线程中执行的,扩容触发的条件是size : used = 1。当把ht[0]中的数据都移动到ht[1]之后,会把ht[0]指向ht[1],ht[1]=null

在移动数据过程中如果客户端进行了更新操作,Redis会操作两个dictht,先去老数组ht[0]中找,如果没有找到就直接去新数组ht[1]中操作,如果老数组找到了就是在老数组中去操作,同时会把这个hash桶中的数据全都移动到新数组中去。

typedef struct dictht {
   
    dictEntry **table;
    unsigned long size;		// hash桶个数、数组的长度
    unsigned long sizemask;	// size-1  计算key在数组中的下标时 hash(key)%2^n == hash(key) & (2^n-1),位运行要快,sizemask存在的意义
    unsigned long used;			// 已经存在了多少个元素,不是使用hash桶的个数
} dictht;

我们现在知道了dictht的作用与结构,那么也就知道Key-value其实存储在这其中的,具体就是存储在dictEntry指针中的,如下所示

dictEntry代码如下所示,其实就是存储了key、value、next三个元素

typedef struct dictEntry {
   
    void *key;		// 这个指针就是指向的一个SDS的对象
    union {
   
        void *val;   // void *表示一个空指针,可以指向任意的数据类型,实际上指向下面的redisObject对象
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;		// 产生hash冲突后,链表就是靠next实现的
} dictEntry;

val这个指针指向的对象类型可能是String、hash、list、set、zset…,当然Redis中也不是简单的直接用这些类型对象,而是用了redisObject 对象来封装

typedef struct redisObject {
   
	// type当key不存在时根据客户端执行的命令确定value是什么类型,比如set命令->string。
    // 当key存在时会约束客户端命令的操作,比如name是一个string类型,但是我却执行lpush name [value...]那么这里直接就抛错了
    unsigned type:4;  
    unsigned encoding:4;	// 某个key对应的value,这个value在redis内存中到底是什么类型的编码
    unsigned lru:LRU_BITS;  // 设置了内存淘汰策略时才会用到
    int refcount;			// 类似于java的垃圾标记算法——引用计数法,用这个来管理内存回收
    void *ptr;				// 指向value内存中真实存储的位置
} robj;

RedisDB主体数据结构如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WOuauZEM-1679905101672)(picture/Redis/RedisDB主体数据结构.png)]



Key

Redis中所有的key都是String类型的,底层是使用的SDS类型,没有使用c语言的字符数组去实现字符串。因为c语言是以\0作为字符串结束标识的,redis需要支持各种语言,当数据以stream流的形式传输到Redis-server后可能某个字符串中就包含这个\0字符。

SDS simple dynamic string

关键特点是:

  • 二进制安全的数据结构

    它有一个属性指定了当前字符串的长度,然后根据这个长度去读取字符串,而不是根据\0作为结束标识

  • 提供了内存预分配机制,避免频繁的内存分配

    如果我们使用append等命令修改一个字符串时,会去判断当前剩余空间是否足够,如果不足够这则按照(length + addlen) * 2去重新分配内存 创建spring对象,当达到了1024*1024后 也就是1M后就会按照每次增加1M去扩容

  • 兼容c语言的函数库

    会自动的在字符串的结尾添加\0 去兼容c语言的函数库

SDS
	free:6
	len:10
	ch
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值