Redis数据结构

SDS:简单动态字符串(enbster)

  1. 数据结构:
    在这里插入图片描述
  2. 杜绝缓存溢出:
    当需要对SDS进行修改时,会先检查SDS的空间是否满足要求,如果不能,就会自动将SDS的空间扩展至所需大小,在执行修改工作。
  3. 内存分配策略
    3.1 空间预分配(用于优化SDS的增长操作)
    如果SDS空间需要扩展,redis不仅会给SDS分配所需的空间,还会额外分配未使用的空间。未使用空间的大小由SDS的长度决定。如果SDS长度小于1MB,则未使用空间free = 使用空间len;如果大于1MB,则每次分配1MB的未使用空间。
    3.2 惰性空间释放(用于优化SDS的缩短操作)
    在字符串修改之后缩短时,SDS会有多余的空间,这个空间并不会被立即释放,而是保留下来用于SDS增长时的需求。

链表(linkedlist)

  1. 链表结构实现
    在这里插入图片描述

字典(HT)

字典,又称为符号表,关联数据或者映射,是用于保存键值对的key - value 的抽象数据结构。它的每个键值都是独一无二的

  1. 数据结构
    在这里插入图片描述
    在这里插入图片描述
  2. 哈希算法
    Redis会通过计算键值得哈希值来确定在数组中的索引位置,在将键值对的哈希表节点以链表的形式放在指定索引上。计算哈希值得算法是MurmurHash2算法。
  3. 哈希冲突
    当两个或两个以上的键被分配到同一个索引上面时,就会产生哈希冲突。redis使用链地址法来解决键冲突。每一个哈希表节点都维护一个next指针,新节点会被添加到链表的头结点位置,它的next指针指向已经存在的节点。
  4. rehash
    当哈希表的键值对数量太多或者太少时,程序会对哈希表的大小进行相应的扩展或者收缩,这是通过rehash来实现的。
    进行rehash的条件:
    负载因子 = 哈希表已保存节点数量 / 哈希表大小
    如果服务器没有执行BGSAVE或BGREWRITEAOF命令,且负载因子大于等于1时,需要对哈希表进行扩展;
    如果服务器正在执行BGSAVE或者BGREWIRTEAOF命令,且负载因子大于等于5时,也要执行扩展;
    如果负载因子小于0.1时,需要对哈希表执行收缩操作。
    rehash实现过程
    为字典的ht[1]哈希表分配空间(如果是扩展,则ht[1]的大小为第一个ht[0].used*2的2^n;如果是收缩,ht[1]的大小为第一个ht[0].used的2 ^n)
    将保存在ht[0]中的所有键值对rehash到ht[1]中:rehash指重新计算哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
    当ht[0]所有的键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。
    渐进式rehash
    rehash过程并不是一次性完成的,而是渐进的、分多次的完成。
    因为如果键值对数量比较大的时候,要一次性将ht[0]的数据全部rehash到ht[1]中会导致服务器在一段时间内停止服务。
    1)为ht[1]分为空间。让字典同时持有ht[0] 和 ht[1]两个哈希表
    2)在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash正式开始。
    3)在rehash进行期间,每次对字典进行增删改查,除了执行指定操作外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性+1;
    4)直到ht[0]上所有的键值对都被rehash到ht[1],这是程序将rehash属性的值设置为-1,表示操作已经完成。

跳跃表(skiplist)

跳跃表是一种有序的数据结构与,它在每个节点都维持多个指向其他节点的指针,从而能实现快速访问。
查找复杂度:平均:O(logn);最坏:O(N)
作用:是有序集合键的底层实现之一,如果有序集合包含的元素数量比较多或者成员是比较长的字符串时,Redis就会使用跳表来作为底层实现。
redis用到链表的地方:有序集合键、集群节点中用作内部数据结构。
在这里插入图片描述

整数集合(intset)

整数集合是集合键的底层实现之一。当一个集合只包含整数值元素,且这个集合元素数量不多时,就会使用整数结合作为集合键的底层实现。

  1. 数据结构
    在这里插入图片描述
  2. 升级
    当编码方式为int16时,新添加的元素是一个int64类型的整数,这时候集合所有的元素都会被转换成int64类型。这就整数集合的升级。
    升级步骤:
    1)根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元分配空间。
    2)将底层数据所有的元素都转换成和新元素相同的类型,并将转换后的元素放到正确的位置,同时需要维护底层数组的有序性不变。
    3)将新元素添加到底层数组中。
    在这里插入图片描述
    升级的优势:提升灵活性;节约内存。
  3. 降级
    整数集合不支持降级操作,一旦数组升级之后,编码状态就会一致保存保存在升级之后的状态。

压缩列表(ziplist)

压缩列表是列表键和哈希建的底层实现之一,如果列表建只包含少量列表项,且每个列表项是小整数值或短字符串,redis就会使用压缩列表来实现底层。

  1. 数据结构
    在这里插入图片描述
  2. 连锁更新
    因为enrty中有个属性是privious_entry_length,这个属性的长度是由前一个节点长度决定的,如果前一个节点长度小于254字节,则privious_entry_length只需要1个字节长,但如果大于等254字节,则需要5个字节长。
    假设当前有若干个entry,每个节点长度都在250-253字节之间。这个时候,因为每个节点的长度都没有超过254字节,所以所有节点的privious_entry_length属性长度都是1字节。但是当我们新插入一个节点A,它的长度达到了254字节,则它的后一个节点B为了能表示新节点A的长度,主要将原本1字节长的privious_entry_length扩展成5字节,这时节点B本身的长度也到达了254(或以上),同理,节点B的后一个节点C为了能表示节点B的长度,也需要将privious_entry_length属性的长度扩展。。。也就是说,节点A后面的所有节点都需要进行扩展,这就会造成连锁更新。

对象

redis使用对象来表示数据库的键和值。每次创建一个新的键值对时,就会至少创建两个对象,一个表示键,一个表示值。

typrof struct redisObject{
	//类型
	unsigned type:4;
	//编码
	unsigned encoding:4;
	//指向底层实现数据结构的指针
	void *ptr;
	...
}
  1. 类型
常用类型对象的名称添加操作
REDIS_STRING字符串对象SET
REDIS_LIST列表对象RPUSH
REDIS_HASH哈希对象HMSET
REDIS_SET集合对象SADD
REDIS_ZSET有序集合对象ZADD
  1. 编码
    在这里插入图片描述

字符串对象

字符串对象的编码可以是 int、raw 或者embstr
在这里插入图片描述
编码转换:对于int编码的字符串对象来说,如果执行的操作是的这个对象保存的不再是整数值而是字符串值,那么字符串编码会从int变成raw;对于embstr编码的字符串对象实际上是只读的,如果要修改,会先将对象的编码从embstr转换成raw。再执行修改命令。

列表对象

列表对象的编码有两种:ziplist 或者 liknedlist
当列表对象保存的所有字符串元素长度都小于64字节,且列表保存的元素数量小于512个,则使用ziplist编码保存数据。
RPUSH numbers 1 “three” 5
在这里插入图片描述
而不过上述两个条件中的任意一个不满足,就会使用linkedlist编码保存数据
在这里插入图片描述

哈希对象

哈希对象的编码有两种:ziplist 或者 hashtable(ht)
HSET profile name “Tom”
HSET profile age 25
HSET profile career “Programmer”
哈希对象保存的所有键值对的长度都小于64,且键值对数量小于512个,使用ziplist编码保存数据
ziplist编码格式
在这里插入图片描述
在这里插入图片描述
不能满足上述任一条件,都是使用hashtable来保存数据。
hashtable(ht)字典编码
在这里插入图片描述

集合对象

集合对象的编码有两种:intset 和 hashtable
当集合对象保存的所有元素都是整数,且保存的元素数量不超过512个使用intset编码
在这里插入图片描述
如果上述条件有一个不满足就使用hashtable编码
在这里插入图片描述

有序集合对象

有序集合编码有两种: ziplist 和 sliplist

有序集合对象保存的元素小于128个且所有保存的元素长度小于64字节,有序集合对象底层编码是ziplist;
在这里插入图片描述
压缩列表的集合元素按分值从小到大进行排序,表头分值小,表尾分值大。

如果不满足上述两个条件的任意一个,就只能使用skiplist编码。
在这里插入图片描述skiplist编码使用zset结构作为底层,一个zset维护一个字典和一个跳表。跳表按分值从小到大保存,可用于实现ZRANK、ZRANGE等命令。字典维护了一个成员到分值的映射。可以用o(1)复杂度查找给定成员的分值。
同时使用跳表和字典是因为:跳表适用于范围查询,但查找成员分值这个操作的复杂度为o(logN),而字典可以降低到o(1)。

内存回收:计数

为一个对象都维护一个refcount(引用计数),在创建一个新对象时,引用计数的值会被初始化为1,而对象被一个新程序使用时,它的应用计数值会增1。当新程序不使用时,会减1。当引用计数值降为0时,所占用的内存就会被释放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值