摘自为了拿捏 Redis 数据结构,我画了 40 张图(完整版)_redis中的平面-优快云博客
String的底层是(简单动态字符串SDS)
List的底层是(quicklist(双向链表,压缩列表))
Hash的底层是(listpack,哈希表)
Set的底层是(哈希表,整数集合)
ZSet底层(listpack,跳表)
哈希桶存放的是指向键值对数据的指针(dictEntry*),这样通过指针就能找到键值对数据.
SDS
内容
len:字符串长度,获取字符串长度时间复杂度为O(1)
alloc:分配的空间长度,修改字符串时,通过alloc-len计算剩余空间大小,进而判断剩余空间是否满足,如果不满足,则会自动扩展,无需手动分配也不会出现缓冲区溢出问题
flags:sds类型,表示不同类型的sds,保存不同大小的字符串,节省空间。同时设计__attribute__ ((packed))属性,按照实际字节对齐,节省内存
buf []:字节数据,保存实际数据,不仅可以保存字符串还可以保存二进制数据
优点:高效长度计算,高效修改,二进制安全,节省内存
双向链表
在ListNode基础上再次封装list数据结构
结构:
head:头指针
tail:尾指针,指向最后一个node,因此获取第一个和最后一个node时间复杂度为O(1)
len:节点数量,获取节点数量时间复杂度为O(1)
dup,free,match函数为节点设置类型,因此链表节点可以保存不同类型的值
缺陷:内存不连续,无法很好利用cpu缓存;链表结构内存开销大
压缩列表ziplist
内存紧凑,占用一整块内存空间,可以利用cpu缓存
结构
表头 + entry + 表尾zlend
zlbytes:记录整个压缩链表内存大小;zltail:列表尾偏移量,尾部节点距起始地址字节;zllen:节点数量;zlend,结尾点,0xFF(255)
entry:prevlen:前一个节点长度,如果前一个节点长度小于254字节,那么prevlen属性占用1个字节,否则占用5个字节
encoding:当前entry数据类型和长度,当前节点数据为整数,则使用1字节;为字符串,则根据大小,使用1/2/5个字节
data:数据
优点:节省内存
缺陷:查找复杂度高;连锁更新,重新分配内存,因此适用于节点不多场景
哈希表 hash
数组+哈希桶
优点:以O(1)时间复杂度查询数据
使用链式哈希解决哈希冲突
rehash
Redis定义俩个哈希表
步骤:1.先将数据写入哈希表1,哈希表2此时为分配空间
2.触发rehash时,给哈希表2分配空间,一般是哈希表的两倍
3.将哈希表1数据迁入哈希表2
4.迁移完成,哈希表1空间释放,将哈希表2设置为哈希表1,哈希表1设置为哈希表2
缺陷:如果哈希表1数据量比较大,迁移数据时间长,阻塞Redis
改进:使用渐进式rehash,分多次迁移
触发条件
负载因子 (已保存节点数量/哈希表大小),当负载因子>=1,并且Redis没有执行RDB快照或AOF时;当负载因子>=5时,不论有没有执行RDB和AOF重写,都会rehash
整数集合 inset
结构
encoding:编码方式
length:元素数量
contents:保存元素的数组
整数集合升级:当集合中的元素元素都是同一种类型时,如int16,那么每个元素就占用16个字节,如果新增元素为int32,那么所有元素都会扩展成int32
优点:节省内存
不支持降级
跳表 zskiplist
多层有序链表,比如一个链表1-》2-》3-》4-》5
头节点有3层,L2,L1,L0
L0层5个节点,L0-》1-》2-》3-》4-》5
L1层3个节点,L1-》2-》5
L2层1个节点,L2-》3
查找4这个节点,先通过L2层查找到3,然后遍历到4,只查找两次
quicklist
(双向链表+压缩列表)
通过控制链表节点中的压缩列表的大小或元素个数,避免连锁更新问题。
quicklist结构
head(链表头),tail(链表尾),count(所有元素个数),len(quicklistNodes个数)
quiclistNode结构
prev(前一个quicklistNode),next(后一个quicklistNode),zl(压缩列表字节大小),sz(压缩列表元素个数),count(ziplist元素个数)
listpack
特点:节点中不包含前一个节点的长度,从而避免连锁更新
listpack 总字节数
listpack 元素数量
listpack entry包含:encoding(元素编码类型),data(实际存放的数据),len(encoding+data的总长度)