《Redis设计与实现》之数据结构与对象

第一遍略读笔记

字符串

SDS用法:字符串值,AOF缓冲区,输入缓冲区
sds={
int len; //判定字符串是否结束
int free; //表示空闲长度
char buf[]; //保存二进制数据
}
容易获取长度
不会造成缓冲区溢出
减少修改字符串长度所需内存分配次数
二进制安全
兼容c字符串安徽念书

链表

在这里插入图片描述
列表键,发布订阅,慢查询,监视器
node={prev,next,value} 双端队列
list={head,tail,len}
dup fee match

字典

普通哈希表结构

Node[] table;
Node{hash,key,value,next}

字典结构 一个哈希表有多个哈希表

## 哈希表节点
dictEntry={key,value,dictEntry next}   
## 哈希表
dictht={dictEntry[],size,mask,used} 哈希表节点,size是哈希表大小,mask=size-1,used哈希表已有的节点
## 字典,有两个哈希表
dict={type,data,dictht[2],rehashinx}  字典:data和type为针对不同类型键值对设计,dictht[1]扩容使用

在这里插入图片描述

扩容
没执行bgsave,bgrewriteaof命令,load_factor>=1
执行bgsave,bgrewriteaof命令,load_factor>=5
在子进程存在期间,提高扩容所需负载因子,避免再子进程存在期间进行扩容。

渐进式rehash
若有成千上万个键值对,一次重rehash回使得服务在一段时间停止服务。

总体思路:分而治之,将rehash工作量分摊到对字段的其他操作中,避免集中rehash带来大量计算。但是在rehash期间,字段的删改查操作都会执行两次,新增只会在新哈希表执行

  1. 为ht[1]分配空间,让字典同时持有两个哈希表
  2. 设置rehashidx=0表示正在rehash
  3. 在rehash期间,每次对字段执行增删改,除了执行指定操作外,还会将ht[0]哈希表在rehashidx索引上所有键值对都复制到ht[1]上,最后rehashidx属性自增
  4. 随着字典操作不断进行,最终ht[0]哈希表所有键值对都重哈希到ht[1]上

有序集合

zskiplistNode={
	level={next,span},
	score,
	object
}
zskiplist={header,tail,level,length}   //level最大层数,length个数

跳跃表是一种有序数据结构。它通过每个节点维持多个指向其他节点的指针,达到快速访问节点的目的。
支持平均0(lonN),最坏0(N)复杂度进行查找节点。还可以通过顺序操作批量处理节点。
跳跃表的效率可以和平衡树相媲美,而且跳跃表实现比平衡树更加简单,有很多程序使用跳跃表来代替平衡树。

redis使用跳跃表作为有序集合键的底层实现之一,若有序集合较多,有序集合元素成员是较长字符串,就会选用跳跃表

举例
fruit-price是一个有序集合键,以水果为value,水果价格为score
zrange fruit-price 0 2  顺序查询前3个水果的价格
分析:
fruit-price有序集合键都保存在一个跳跃表中。

压缩列表

是为了节约内存开发得,是由连续内存块组成得顺序型数据结构。
底层很复杂待学习

整数集合

redis对象

Redis并没有直接使用这些数据结构实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含五种类型对象:字符串对象,列表对象,集合对象,字典对象,有序列表对象
redis为何要构建一个对象系统呢??

  • 执行命令前,可根据对象类型判断此对象是否可执行该命令
  • 针对不同应用场景,为对象设置多种不同得数据结构实现。
  • 实现基于引用计数技术得内存回收机制,当不使用对象时自动回收
  • 携带访问时间记录信息,可计算数据库键的空转时长,从而优化回收

对象类型和编码

redis使用对象表示数据库的键和值,每当每次插入一个键值对时,至少创建两个对象**{键对象+值对象}**

typedef struct redisObject{
  unsigned type;           // 定义类型
  unsigned encoding;       //定义编码
  void *ptr;               //指向底层数据结构的执行
}
  • 类型
    type命令返回的是值对象的类型。
    在这里插入图片描述
  • 编码
    记录对象使用哪种数据结构,每种数据结构有自己的编码
    每种对象至少使用两种编码
    object encoding key 查看编码
    在这里插入图片描述
    几大提升了redis的灵活率,比如列表对象元素比较少时使用压缩列表,元素比较多时使用双端队列。

字符串对象

编码可以是int,raw,embstr
int: 整形值
raw:字符串值且长度大于32字节,简单动态sds
embstr:字符串值且长度小于32字节,简单动态sds 所有数据都存储在一块连续内存,只读

列表对象

编码可以是ziplist和linkedlist

rpush numbers 1 "three" 5
  • ziplist使用压缩列表作为底层实现
    使用情况:
  • 列表对象所有字符串长度都小于64字节
  • 列表对象元素数量小于512个
    在这里插入图片描述
  • linkedlist使用双端队列实现
    在这里插入图片描述

哈希对象

编码可以是ziplist或者hashtable

  • ziplist使用压缩列表实现
  1. 哈希对象所有键和值都小于64字节
  2. 哈希对象键值对数量小于512个
    在这里插入图片描述

hashtable使用字典实现
哈希对象的每个键值对 都使用字典的键值对来保存下图有点问题
在这里插入图片描述

集合对象

编码可以是intset或者hashtable
sadd numbers 1 3 5

  • intset编码使用整数集合实现
  • 集合对象保存的都是整数值
  • 集合对象元素个数不超过512个
    在这里插入图片描述
  • hashtable使用字段实现
    在这里插入图片描述

有序集合对象

zadd price apple 8.5 banana 5.0 cherry 6.0
编码可以是ziplist或者skplist

  • ziplist使用压缩列表实现
    在这里插入图片描述
  • ziplist使用跳跃表+字典实现
    dict字典的哈希表都保存了一个集合元素,字典的键保存了value,字典的值保存了score。
  1. 保存元素成员长度都小于64字节
  2. 有序集合元素数量小于128个
    在这里插入图片描述
    redis为何同时使用跳跃表和字典来实现有序集合呢,而且还会保存两次???
    理论上可以单独使用一种数据结构实现,但同时使用的性能会更高
  • 如果我们只使用字段保存,保留了以0(1)时间复杂度查询成员的优点,但是zrank,zrange等排序命令,则需要再次进行排序,完成排序至少0(NlogN)。
  • 如果我们只使用跳跃表实现有序集合,排序可以很快完成,但是查找又从0(1)上升到0(lonN)
  • 最终字典和跳跃表会共享元素成员和分值,不会造成任何内存浪费

类型检查与多态命令

llen命令:确保执行命令的是列表键,而且要好根据值对象的编码,使用正确的实现

  • 类型检查:为了确保只有指定类型的键可以执行某种特定操作,根据值对象类型进行检查
  • 多态命令:为了同一条命令可以处理不同值类型,可以根据值对象编码使用正确的实现

内存回收和共享

内存回收

c语言不具备自动内存回收功能。
解决:redis使用引用计数实现了内存自动回收机制。

  1. 创建新对象时,引用计数初始化为1
  2. 被调用时自增1
  3. 不被使用时自减1
  4. 当引用计数变为0时,对象占用内存将被释放

内存共享

引用计数器还能实现对象共享功能。
redis会共享值为0到9999的字符串对象

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值