String,List,hash,set,zset那么它们的底层实现是什么,redis系统又对这些数据进行了什么管理,这里就仔细分析一下。以下是redis数据结构的层次图。




实现redis存储嵌套map的工具类 redis 多层嵌套对象_实现redis存储嵌套map的工具类


1.RedisObject


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;


Redis并没用直接使用5种数据类型,而是将他们封装成一个统一的对象robj。

type值表示对象的属性也就是那5种类型。

encoding值是底层实现类型。

refcount是对象的引用次数,redis里面使用的是引用计数法去管理内存(因为系统已经是写死的,不会出现相互引用的情况),在每次释放资源的时候就会-1并检查是否为0为0就释放。

lru是这个对象最后一次被访问的时间,用于内存管理如果系统开了maxmemory并且服务器的内存回收算法是lru那么内存满后就会回收这些时间长的对象。

2.SDS(简单动态字符串)


struct sdshdr {
    unsigned int len;   //buf中已经使用的长度
    unsigned int free;  //buf中未使用的长度
    char buf[];         //柔性数组buf
}


这里对于字符串的存储通过一个char数组进行存储,len表示长度,free表示剩余长度

空间预分配:redis数据操作次数多,那么每次对于字符串操作就分配新的内存是不现实的。所以给字符串操作后分配多一点的内存是redis系统中的一个功能。比如将二个长度字符串为5的合并在一起,如果一个sds的buf长度不够,那么就会分配10(合并长度)+10(预分配长度)的长度。

二进制安全: 不同于C语言以0结尾,redis提取字符串是依据长度提取这样就保证安全。

3.embstr

embedded),当长度超过44时,使用raw形式存储

embstr存储形式是这样一种存储形式,它将RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。而raw存储形式不一样,它需要两次 malloc,两个对象头在内存地址上一般是不连续的

4.ziplist

特殊编码的连续内存块组成的顺序型(sequential)数据结构


实现redis存储嵌套map的工具类 redis 多层嵌套对象_字符串_02


5.Linkedlist

常规的双向链表,redis功能比如列表键、发布订阅、监视器等都使用这一结构

6.字典/ht(数据库里面的key-value存储,过期时间表的存储等等都用到字典)


typedef struct dict{
   //类型特定函数
	dictType *type;
	//私有数据
	void *privdata;
	//哈希表
	dictht ht[2];
	//rehash索引
	//当rehash不在进行时,值为-1
	int rehashidx;
}dict;


redis里面哈希表(dictht)底层是通过数组+链表的形式组成的。而字典里面对于哈希表进行了一次包装,里面包含有两个哈希表,使用2个哈希表是为了rehash的均摊做准备。

rehash:通常的rehash比如java里面是创建一个两倍的entry数组然后把数据重新hash一遍。但是一遍rehash过于耗时,redis采取渐进式的方法去rehash。

假设当前数据在ht[0]中并且数据已经过了阈值那么就会分配一个两倍ht[0]的数组到ht[1]中,在字典中维持一个索引计数器rehashidx,每次对于字典的操作都会让rehashidx加一并对于ht[0]中的对应行ht[0][rehashidx]中的数据重新hash到ht[1]表中。

注意:程序在执行bgsave或者bgrewriteof的时候不能rehash

7.intset


typedef struct intset {
    uint32_t encoding;  // 编码类型 int16_t、int32_t、int64_t
    uint32_t length;    // 长度 最大长度:2^32
    int8_t contents[];  // 柔性数组
} intset;


整数数据有序的存储在contents数组里面,通过二分查找来查找元素

8.skiplist-东西有点多引用一个讲的好的链接

https:// blog.csdn.net/yellowriv er007/article/details/79021103

9.5种数据格式总结

String类型的ptr指针可以指向一个long整数也可以指向一个sds或者embstr对象

List类型指向一个ziplist对象或者linkedlist对象

hash类型指向一个ziplist对象或者字典对象

Set类型指向一个intset对象或者字典对象

ZSet类型指向一个ziplist对象或者跳跃表+字典的组合结构对象

这里跳跃表加字典的组合结构,就是同时用字典和跳跃表去存储成员和score,优点是能以字典的特性在O(1)的时间查找某个值,以跳跃表的特性O(logN)的时间去查询范围。

以上结构(hash,list,zset)使用ziplist都是在数据量少的情况下,当数据量变多的时候就会将ziplist里面的数据转移到后面的对象中

同理String类型如果给一个整数就用int存,如果变成小的字符串就升级成embstr,如果字符串变大就升级成sds。

Set如果最开始添加了一堆整数就是intset,加入了一些字符串或者元素个数超过512个就升级成字典。