redis 5种对象类型对应的编码类型 及 数据结构

一. 数据结构

SDS简单动态字符串(string)

      Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串( simple dynamic string,SDS)的抽象类型,并将SDS用作 Redis的默认字符串表示,是可以修改的字符串,在内存中它是以字节数组的形式存在的
在这里插入图片描述
1. 内存扩容策略:空间预分配

  1. 修改后sds的len小于1MB,free=len
  2. 修改后sds的len大于1MB,free=1MB

2. 内存缩容策略:惰性释放

  1. sds的len缩短时,程序并不会立即使用内存重分配来回收缩短后多出来的字符,而是使用free记录,有需要时真正释放未使用空间,并不会造成内存浪费

空间预分配和惰性释放减少了sds的内存重分配

3. sds和C字符串对比

SDSC字符串
获取字符串长度复杂度O(1) :取len值O(n):从头开始遍历知道遇到’\0’空字符结束,并不记录自身长度
API是否二进制安全安全:用len最为字符串结束标志,可保存文本和图片,音频等任意格式的二进制数据不安全:用空字符组为字符串结束标志,只能保存文本格式数据
缓冲区是否会溢出不会:在修改之前自动检查了sds空间是否足够,即free长度会:修改字符串时内存已不够
内存重分配次数减少内存重分配次数:修改N次字符串最多执行N次内存重分配修改N次字符串必然执行N次内存重分配

dict字典(set,zset,hash)

      c是没有字典这种数据结构的,所以redis自己实现了字典这种数据结构,每个键都是唯一的,类似java中的,Hash字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多或键值对中的元素都是较长字符串时,redis就会使用字典作为哈希键的底层实现
字典使用哈希表作为底层实现,一个哈希表可以有多个哈希表节点,每个哈希表节点保存了字典中的一个键值对,当有键冲突时,哈希表节点就用next指针构成一个单项链表,next即为分配到同一索引上的节点
在这里插入图片描述
1.扩容策略
      负载因子=哈希表节点已保存节点数量/哈希表大小:load_factor=ht[0].used / ht[0].size

      哈希表的扩展与收缩当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作

  1. 服务器目前没有在执行 BGSAVE命令或者 BGREWRITEAOF命令,并且哈希表的负载因子大于等于1
  2. 服务器目前正在执行 BGSAVE命令或者 BGREWRITEAOF命令,并且哈希表的负载因子大于等于5

      根据程序当前是否在进行 BGSAVE 相关操作,扩容需要的负载因子条件不相同,这是因为在进行 BGSAVE 操作时,存在子进程,操作系统会使用 写时复制 (Copy On Write) 来优化子进程的效率。Redis 尽量避免在存在子进程的时候进行扩容(因为进行rehash时也浪费了h[0]占用的内存),尽量的节省内存

2.缩容策略
      当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作

3.渐进式rehash过程
       rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上(发生在缩容和扩容时机)

  1. 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
  2. 在字典中维持一个索引计数器变量 rehashidx,并将它的值设置为0,表示 rehash工作正式开始
  3. 在 rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在 rehashidx索引上的所有键值对rehash到ht[1],当 rehash工作完成之后,程序将 rehashidx属性的值增一
  4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1] (ht[0]变为空表),释放ht[0],这时程序将 rehashidx属性的值设为-1,并在ht[1]新创建一个空白哈希表,为下一次 rehash做准备,表示 rehash操作已完成

      渐进式 rehash的好处在于它采取分而治之的方式,将 rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式 rehash而带来的庞大计算量

      为字典的ht[1]哈希表分配空间时,这个哈希表的空间大小取决于要执行的操作,以及ht[O]当前包含的键值对数量(也即是ht[0].used属性的值)

  1. 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2即2n(2的n次方幂)
  2. 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[o].used即2n

4.rehash期间的哈希表操作

  1. 字典的删除( delete)、查找(fnd)、更新( update)等操作会在两个哈希表上进行。例如,要在字典里面查找一个键的话,程序会先在ht[0]里面进行查找,如果没找到的话,就会继续到ht[1]里面进行查找
  2. 新添加到字典的键值对一律会被保存到ht[1]里面,而ht[0]则不再进行任何添加操作,这一措施保证了ht[0]包含的键值对数量会只减不增,并随着 rehash操作的执行而最终变成空表

5.Redis 的字典结构在 rehash 时和 Java 的 HashMap 的 Rehash不同:
       Java 的 HashMap 实现方式是 :新建一个哈希表,一次性的将当前所有节点 rehash 完成,之后释放掉原有的 hash 表;而Redis 使用的是渐进式 hash 的方式

双端无环链表(list)

      c是没有链表这种数据结构的,所以redis自己实现了链表,可以理解为java中的List(但结构有区别),链表redis中应用还是很广的,比如,当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时, Redis就会使用链表作为列表键的底层实现
在这里插入图片描述
1.优点:

  1. 获取长度复杂度为O(1)
  2. 因为每个节点都持有perv和next指针,所以获取前后节点的复杂度为O(1)
  3. 因为list结构有head和tail指针,所以获取链表的头和尾节点的复杂度为O(1)
  4. list结构通过dup(复制链表节点保存的值)、free(释放链表节点保存的值)、match(对比链表节点保存的值和另一个输入值是否相等)属性为节点设置类型特定函数,所以链表可以保存各种类型的值

2.缺点:

  1. 查找某个元素时间复杂度O(N)
  2. 指针占用内存有点多,内存浪费
  3. 每个节点单独的进行内存分配,当节点过多,造成的内存碎片太多了。影响内存管理的效率

intset 整数集合(set)

      当一个集合只包含整数值元素,并且这个集合的元素数量不多时, Redis就会使用整数集合作为集合键的底层实现,集合是从小到大有序的

升级

  1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间
  2. 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变
  3. 将新元素添加到底层数组里面
  4. 修改length属性值

引起升级元素的存放位置

      因为引起升级的元素要么就大于所有现有元素,要么就小于所有现有元素,所以

  1. 小于:新元素放在在索引为0处
  2. 大于:新元素放在索引为length-1处

intset不支持降级操作,即一旦对数据进行升级,编码就会一直保持升级后的状态

优点:

  1. 用能容纳数字的最小编码进行存储,可以有效的节约内存
  2. 整数集合封装了对int16t、int32t、int64t三种整数之间的转换,使用时我们不用考虑类型错误,可以不断的向整数集合内添加整数。提升了操作的灵活性(c是静态编译的)

缺点:

  1. 每次向整数集合添加新元素都可能会引起升级,而每次升级都需要对底层数组中已有的所有元素进行类型转换和内存重分配,所以向整数集合添加新元素的时间复杂度为O(n)

skiplist跳跃表(zset)

      当一个有序集合中的元素数量比较多,又或者有序集合中元素是比较长的字符串,redis就会使用跳跃表作为有序集合的底层实现
在这里插入图片描述
注意点

  1. 每个节点的层高为1到32之间的随机数
  2. 各节点保存的对象唯一,当多个节点保存的分值score可以相同
  3. 分值相同时,节点按成员对象的大小排序,对象小的放前面节点,大的放后面节点

ziplist 压缩列表(list,hash,zset,hash)

      当列表中指包含少量元素,且每个元素要么就是小整数值,要么就是长度比较短的字符串,redis就会是哦那个压缩列表作为列表键的底层实现。
压缩列表是redis为了节约内存而开发的一种数据结构,是由一系列特殊编码的连续内存块组成的数序星数据结构。一个压缩列表可包含任意多个节点,每个节点保存一个字节数组或一个整数值,redis为了节约内存

在这里插入图片描述
优点

  1. 节约内存

缺点

  1. 新增或删除节点时,可能引起连锁更新错做,但出现的几率不高,所以不用过多担心,即使出现,只要更新节点数量不多,影响也不大

      上面介绍了redis常用的数据结构,但是redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们前面所介绍的数据结构。由此可以得出:每次当我们在 Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)

二. 对象

       Redis中的每个对象都由一个 redisobject结构表示,该结构中和保存数据有关的三个属性分别是type属性、 encoding属性和ptr属性:

typedef struct redisobject
//对象类型,可以是(REDIS_STRING:字符串对象,REDIS_LIST:列表对象,REDIS_HASH:哈希对象,REDIS_SET:集合对象,REDIS_ZSET:有序集合对象)中一个
unsigned type:4 ;
//对象使用的编码,即对象使用了什么数据结构作为对象底层实现
unsigned encoding: 4;
/指向对象的底层实现数据结构,由encoding决定
 void  *ptr;
 //....

下面表格列出了每种对象使用的编码格式和使用情况:
在这里插入图片描述

对象类型

1.字符串对象
编码类型数据结构使用情况转换
int字符串保存的是整数值int->raw
embstr字符串保存的是字符串,且长度小于32字节embstr->raw
row字符串保存的是字符串,且长度大于32字节
2.列表对象
编码类型数据结构使用情况转换
ziplist压缩列表列表所有元素为整数或字符串长度小于64自己,且元素个数小于512个ziplist->linkedList
linkedlist双端列表不满足ziplist的任意一个即可
3. 哈希对象
编码类型数据结构使用情况转换
ziplist压缩列表所有键值对的键和值的字符串小于64自己,且键值对个数小于512(添加时先将键放在压缩列表表尾,再将值放在压缩列表表尾,即键的节点在前,值得节点在后,先存的键值对节点在前,后存的键值对节点在后)ziplist->hashtable
hashtable字典不满足ziplist条件之一(字典中每个键都是一个字符串对象,保存键;每个值都是一个字符串对象,保存键值)
4. 集合对象
编码类型数据结构使用情况转换
intset整数集所有元素为整数值,且元素个数不超过512intset->hashtable
hashtable字典不满足intset条件之一(字典中每个键为字符串对象,每个字符串对象包含一个集合元素,值设为null)
5. 有序集合对象
编码类型数据结构使用情况转换
ziplist压缩列表元素长度小于64自己,且元素个数少于128个(每个元素使用两个紧挨在一起的压缩列表节点保存,第一个节点保存值,第二个节点保存分数)ziplist->skiplist
skiplistzset(跳跃表+字典)不满足ziplist条件之一(zsl跳跃表节点的object保存值,score保存分数;字典的键保存值,字典的值保存分数,通过字典可用O(1)查找多给定成员的分数。zscore命令使用的这一特性。字典和跳跃表会通过指针共享元素的值和分数,所以不会造成数据重复)

对象一些特性

1. 内存回收

      c不具备内存回收功能,redis实现了一种名为"引用计数技术"实现内存回收,通过这一机制,程序可以通过跟踪对象的引用计数信息,在引用计数值变为0时,对象占用的内存被回收,这和下面的共享对象也有关联

typedef struct redisobject{
    //引用计数
	int refcount;
} robi ;
2. 共享对象

      为了节约内存,redis对整数值的字符串对象进行共享,即当有多个键对应的值相同时,数据库只保存一个对象值,通过对象引用机制实现对象共享
redis会在初始化服务器是,创建0到9999的字符串对象,当服务器需要用到这区间的值时,服务器就会使用这些哦共享对象,而不是新建对象

3. 对象的空转时长lru
typedef struct redisobject{
    //记录对象最后一次被命令程序访问的时间
	unsigned lru : 22 ;
} robi ;

      服务器打开maxmemory选项,且内存回收算法为volatile-lru或allkeys-lru时,服务器内存超过配置的maxmemory,空转时长较长的部分键优先被服务器释放,从而回收内存。


以上多半内容来自《Redis设计与实现》摘录,介绍了redis的5种对象类型对应的编码类型 及 数据结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值