Redis底层存储结构三---Redis中Key-Value中的Value

我们都只知道,在我们的Redis中,最常用的Value数据结构有5个,string、list、hash、set、zset,但是这五个在内存中的存储还是有一些变化的,接下来逐个的介绍一下这几个

1、string

对于string形式,我们Redis底层在内存中的存储是由三种结构的,这三种结构分别是int,embstr,raw
在上一篇博客中我们讲到了真正存储Value的那个数据结构有两个属性,一个是type,一个是encoding,这个type是我们外部的属性,都是string,而这个encoding就是内存中的编码,虽然type都是string,但它的数据类型却有更细的分类,就是int,embstr,raw
如下图所示:
type str
object encoding

在这里插入图片描述

1.1、int

当我们set一个整型值的时候,Redis内存中的编码结构就是int类型。

1.2、embstr

当我们set一个比较短的字符串的时候,Redis内存中的编码结构就是embstr类型。

1.3、raw

当我们set一个比较长的字符串的时候,Redis内存中的编码结构就是raw类型。

这个字符串的长度到底有多长,这个和我们CPU的缓存行有关系,在我们这64位的系统中,一般缓存行是64字节,然而我们需要表示一个sds字符串的话,其它的属性需要20个字节,如下所示:
主要来自于redisObject属性以及sds属性:
就我们第二篇博客的最后两个数据结构,如下图所示:

在这里插入图片描述

redisObject总共占16个字节

// type 占 4bit
type
// encoding 占 4bit
encoding
// lru 占 3byte
lru
// refcount 占 4byte
refcount
// *ptr 占 8byte
*ptr

sds总共占4个字节

// 最小的 len 占 1byte
len
// free 占 1byte
alloc
// flags 占 1byte
flags
// \0 占 1byte
buf[]

所以当超过44个byte位之后,就会存储成raw类型,如下图所示:
在这里插入图片描述

2、list

Redis底层的list存储是双端链表(quicklist)和 ziplist 作为List的底层实现, quicklist 是外层实现 ziplist 是内层实现,如下图所示:

2.1、quicklist

在下面这张图中,我们可以很明显的看出 quicklist 是外层,ziplist 是内层, quicklist 专注于整个list的结构,ziplist 主要存储数据

在这里插入图片描述

2.2、ziplist

从这张图中我们就可以看出我们将list的数据分成了很多份,每个ziplist 存储其中的一份。

在这里插入图片描述

2.3、list压缩和数据量过大ziplist分裂的配置

配置含义
list-max-ziplist-size -2单个ziplist节点最大能存储 8kb ,超过则进行分裂,将数据存储在新的ziplist节点中
list-compress-depth 1代表从头节点往后走一个,尾节点往前走一个不用压缩,其他的全部压缩,2,3,4 … 以此类推

其实这些属性的说明也可以在redis.conf配置文件种的注释中看到,如下所示:

在这里插入图片描述

2.4、list为什么不用双向链表作为底层数据结构

因为list可以从前往后访问,也可以从后往前访问,如果说用链表的话每个节点就需要两个指针,每个指针占8个字节,一个节点的两个指针就占16个字节,所以就出现了下面的第一个问题:
1、浪费了大量的内存空间。
2、由于链表的内存空间不是连续的,所以会产生大量的内存碎片。

3、hash

在Redis底层对hash的存储也分为两种,一种是ziplist,还有一种是hashTable。

3.1、ziplist

当数据量比较小的时候,hash会存储为ziplist的形式,如下图所示:

在这里插入图片描述

3.2、hashTable

当元素过多或者单个元素数据过大时,Redis底层就会把hash的存储形式从 ziplist 转换成 hashTable ,这样避免了 ziplist 的访问效率问题。

在这里插入图片描述

3.3、从 ziplist 转换为 hashTable 的条件

在redis.conf配置文件中可以修改以下两个配置,即可改变在什么情况下,数据结构就由 ziplist 转换为 hashTable 。

配置含义
hash-max-ziplist-entries 512ziplist 元素个数超过 512 ,将改为hashtable编码
hash-max-ziplist-value 64单个元素大小超过 64 byte时,将改为hashtable编码

3.4、Redis的渐进式ReHash

如果说一个位桶数组,某一个下标后面的数据非常非常多,而其它下标后面的数据却非常少,那么这个时候Redis就会进行ReHash。
ReHash的意思是会开辟一个为原数组2倍容量的数组,然后对原有数组的所有数组全部进行hash重新存储,但是为了防止数据量过大,全部一次性ReHash的话会影响客户端的操作等,所以就产生了渐进式的Rehash,这样客户端的访问也就不会卡顿了。
渐进式ReHash中如果写入了数据,会去先判断这个数据在老的hashTable中有没有,如果有,则将旧的修改之后搬到新的HashTable中,如果没有,从新的HashTable里面去找,找到则修改,找不到则直接写入到新的HashTable中。

4、set

Set 为无序的,自动去重的集合数据类型,底层也是dict,只可以存储一个null值,并且会自动帮我们去重,有两种存储结构,intset和hashTable。

4.1、intset

intset其实就相当于是一个数组,在set的元素全部都是在范围内的整型值,set底层就会用intset来存储。

4.1.1、intset的数据结构

数据结构如下:

在这里插入图片描述

typedef struct intset {
    uint32_t  encoding;
    uint32_t  length;
    int8_t    contents[];
} intset; 
 
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

在上面代码中也可以看出,对于整型值的大小我们也分为了三种,可以用三种不同的数据结构进行存储,目的也就是为了节省空间,当某一个整型值超过了最大的范围,也就是 264-1,那么就得转换成hashTable的存储形式了。

4.1.2、intset示例

注意:intset还会自动排序。

在这里插入图片描述

从上图就可以看出,我们写入了9个元素,但是写成功了只有7个,去重了两个,然后我们遍历元素时,发现确实给我们排序了,再查看底层编码结构,用的是intset。

4.2、hashTable

我们有以下几个条件会使底层的数据结构从intset编程hashTable
1、单个整型值数据过大,大于 264-1。
2、数据的数量过多。
3、传入了一个数据不能转换为整型数据。
如下图所示:

在这里插入图片描述

4.3、从 intset 转换为 hashTable 的条件

修改下面的配置,即可改变数量。

配置含义
set-max-intset-entries 512intset 能存储的最大元素个数,超过则用hashtable编码

5、zset

zset 是一种有序的set集合,也会自动去重,ZSet 数据结构底层实现为 字典(dict) + 跳表(skiplist) ,当数据比较少时,用ziplist编码结构存储,如下面代码:

typedef struct zset {
	// 跳表		
    zskiplist *zsl;
	// 字典
    dict *dict;

} zset;

5.1、ziplist

对于 zset 的 ziplist 数据结构,其实就是将每一个元素的分值与value值分别存储,用两个entry元素来存储,数组嘛,肯定比链表要节省开销,如下图所示:

在这里插入图片描述

5.2、dict

zset的字典主要维护了元素的Value以及分值,这样就直接可以通过ZSCORE命令由O(1)的时间复杂度拿出分值。

5.3、zskiplist

zskiplist的大致模型如下图所示:

在这里插入图片描述

通过上图我们就可以看出,要查找一个数据时,时间复杂度是O(logN),和我们的二分法查询效率大致一样,里面的level是通过一个zslRandomLevel()函数生成的。

5.4、从 ziplist 转换为 字典(dict) + 跳表(skiplist) 的条件

配置如下参数即可

配置含义
zset-max-ziplist-entries 128元素个数超过128 ,将用skiplist编码
zset-max-ziplist-value 64单个元素大小超过 64 byte, 将用 skiplist编码

10、辅助知识

10.1、对于 2n 的取模

在Java中,对于 2n 取模可以转换成对 2n-1 做&运算,如下代码所示:

int X = NUM % 2^n^
int Y = NUM & ( 2^n^ - 1 )

X 和 Y 的结果是一样的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值