Redis基本数据结构

1.开篇

我们都知道redis五种基本数据结构为string、list、hash、set、zset,那是站在用户的角度上,作为程序员有必要取了解一下这些数据结构的底层构成是哪些基本数据结构,接下俩我们就来一一探究一下。

2.简单动态字符串

没有使用c语言传统的字符串,使用的是自己构建的SDS(simple dynamic string),并将SDS用作Redis字符串的默认表示

除了保存数据库中的字符串以外,SDS还被用做缓冲区

  1. AOF模块中的AOF缓冲区
  2. 客户端状态中的输入缓冲区

2.1 SDS的定义

结构体

struct sdshdr {

  int len; // 记录buf数组中已使用字节的数量
  int free; // 记录buf数组中未使用字节的数量
  char buf[]; // 用于保存字符串
};

在这里插入图片描述

2.2 SDS特别之处,与string.h中的字符串的区别

1.获取长度的时间复杂度为O(1)
2.动态扩展性
1)、空间预分配,当len<1024时,free=len

							当len>=1024时,free=1024

2)、惰性空间释放:也就是说,当对SDS操作导致字符串长度变短时,不会立即释放buf
3.二进制安全

传统的字符串中不能出现转义符空格等,SDS语序,因为SDS的结束是以len来判断的,所以SDS支持二进制的图片、音频等。
二进制安全也支持了Redis的二进制字符串操作。

4.与string有通用的API

在这里插入图片描述

3.链表

简单概括起来就是双端链表,外面包裹一层易于操作的结构体

在这里插入图片描述

使用的位置

  1. 发布订阅
  2. 慢查询
  3. 监视器
  4. list键值对

4.字典

Redis数据库的底层实现,各种数据类型的键值对,过期时间的键值对都是通过字典来实现的。还有hash键的底层实现也是它。

字典的实现,三个数据结构Dict、DictHt、DictEntry

最外层:字典,两个哈希表,用于rehash
在这里插入图片描述

第二层:哈希表

在这里插入图片描述

第三层:具体的键值对节点
在这里插入图片描述

展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wiYXHwMk-1690981389121)(attachment:492fbbd39543400a1c33f8189e07c61c)]

哈希算法

按位与操作计算哈希值,举个例子

在这里插入图片描述
注意:其实这个散列方式与Java中的HashMap相同

解决键冲突

链表法

其他方法

  • 开放定址法
  • 再散列法

rehash

为了让负载因子维持在一个合理的范围,负载因子计算公式

load_factor = ht[0/1].used / ht[0/1].size

翻译过来就是已使用的容量 / 总容量

什么情况下发生rehash呢

情况一:扩容

当负载因子大于1是

  1. 如果没有高内存的使用操作如:BGSAVE或BGREWRITEAOF操作时扩容
  2. 如果有高内存的使用操作,当load_factor>5时扩容

情况二:缩容

当load_factor<0.1时缩容

扩容(缩容)操作是怎样的:渐进式rehash

hashtb容量为size的2次方扩大或者减小,如果目前使用的是ht[0],那么就会把ht[1]初始化为2次方的增大或减小,具体看扩容还是缩容,举个例子如果ht[0].size = 4,如果要扩容,就会把ht[1]初始化为size=8,然后通过渐进式的rehash把hashEntry复制到ht[1]中,所谓的渐进式,就是两个hash表同时存在,api根据具体情况使用,知道rehash结束后,才会把旧的hashtb删除。

与Java中的HashMap有什么不同?

  • HashMap中发生rehash的时候会新建一个hashmap来rehash,redis中的dict中是通过两个hashtb相互拷贝来实现的。
  • hashmap与redis的dict散列方法相同,但是hashmap用到了链表和红黑树,redis中的dict只用到了链表
  • HashMap不保证线程安全,redis是单线程所以线程安全
  • HashMap用迭代器遍历的时候如果发生修改会发生fail-fast,但是redis采用渐进式的方式来处理dict中发生的变化以及访问
  • redis是基于内存的,如果发生BGSAVE或者BGREWRITEAOF等消耗大量内存资源的操作时,会将复制的thresold提升至4,也就是节点数大于链表长度四倍。

5.整数

  • 整数集合是集合键的实现原理之一
  • 整数集合支持升级操作,不支持降级操作,就是数组中默认长度为int16,如果装不下就会升级为int32,再装不下就会升级为int64

6.压缩列表

  • 压缩列表是为了节省内存而存在的数据结构
  • 压缩列表是列表键和集合键的底层实现之一
  • 压缩列表存储了一系列信息,比如:列表的字节数、列表长度、列表尾元素偏移,节点
  • 节点存储了上一个节点的字节数,这个节点的元素类型,元素内容
  • 压缩列表可能发生连锁更新问题,因为当上一个节点的大小大于254时,就会更新下一个节点的保存上一个节点长度的字段,从1字节更改为5字节,所以下一个节点增大了四字节,可能会造成连锁更新问题

7.对象

对象是最对Redis用户所使用的对象而存在的数据结构,如字符串键、列表键、哈希键、集合键、有序集合键。每一个对象都至少用到了两种以上的数据结构实现,接下来我们一一来说明。

字符串键

字符串键的键使用了SDS字符串对象,字符串的值使用三种不同类型对象存储,适应不同情况

  • 存储整数的时候字符串使用int类型
  • 存储长度较小的字符串时使用embstr类型
  • 存储长度较大的字符串时使用sds类型

首先讲下字符串类型要使用这么几种不同数据结构存储,第一个是节省内存,第二个是分配embstr内存只需要一次,而分配sds字符串需要两次
然后讲一下什么情况下发生转化:
1.当字符串长度大于32字节时,值类型存储从embstr转为sds
2.当调用一些函数时也会发生类型转化,例如llen key会将int类型转化为sds来计算

列表键

列表键的底层实现为ziplist或者linkedlist

  • 当列表长度大于512时,列表的底层实现从ziplist转化为linkedlist
  • 当列表中的元素大于等于64字节时,列表的底层实现从ziplist转化为linkedlist
    也就是说,默认情况下,列表键使用ziplist作为底层实现,原因是这样做节省内存空间。当然上述两个条件可以通过配置文件来手动配置。

哈希键

哈希键的底层实现为ziplist或者dict

  • 当列表长度大于512时,列表的底层实现从ziplist转化为dict
  • 当列表中的元素大于等于64字节时,列表的底层实现从ziplist转化为dict
    可以发现,转化条件与列表键几乎相同

集合键

集合键的底层实现为intset整数集合和dict字典

  • 当所有集合元素都是整数时
  • 当元素个数小于512时

满足上述两个条件时,集合键的底层实现为intset,破坏任意一个条件,集合键的底层实现变为字典,当然,hash只用键,值为空作为set用法,屡见不鲜了。

有序集合键

有序集合键的底层实现为ziplist或者dict+skiplist(为啥要用两个,后面解释)

  • 当集合元素个数小于128时
  • 当集合中的任意元素小于64字节时

满足上述两个条件,有序集合键的底层实现为ziplist。
我发现,与列表键和哈希键发生变化时的长度限制不同,有序集合键为128,大概是因为有序集合键有范围操作的函数,128可以保证O(n)的时间复杂度查找时间不会过于久。
然后解释一下为啥 zset这个数据结构要用到dict+skiplist,理论上来说一个数据结构便足以保存key+score的键值对了,解释如下:

  • 适应不同的查找函数,比如范围查找,skiplist比dict快,单个键的查找,dict比skiplist快
  • dict和skiplist共享值对象内存,后续引用计数法会有介绍

Redis中的多态

redis中的命令分为两种类型,一种是可以对所有的键执行,例如DEL、TYPE、RENAME、EXPIRE、OBJECT等。
还有一种命令是针对特征键才能够执行,我截了张图方便看
在这里插入图片描述
还有就是,键的不同数据结构也会导致执行不同的函数

内存回收

关联到对象的两个字段

  • 引用数
  • lru时间

首先,redis对象是否存活,是通过引用计数法的方式来实现的,举个例子redis在服务器初始化时会初始化一块内存,存放0-9999的整数类型,如果键使用到,那个引用数就会加1
当引用数清空时,就会通过lru时间来调度,lru时间记录的是最后一次使用的时间,如果当前时间-lru时间超过设定的阈值,则会对该对象进行内存回收。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值