第一遍略读笔记
字符串
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期间,字段的删改查操作都会执行两次,新增只会在新哈希表执行
- 为ht[1]分配空间,让字典同时持有两个哈希表
- 设置rehashidx=0表示正在rehash
- 在rehash期间,每次对字段执行增删改,除了执行指定操作外,还会将ht[0]哈希表在rehashidx索引上所有键值对都复制到ht[1]上,最后rehashidx属性自增
- 随着字典操作不断进行,最终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使用压缩列表实现
- 哈希对象所有键和值都小于64字节
- 哈希对象键值对数量小于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。
- 保存元素成员长度都小于64字节
- 有序集合元素数量小于128个
redis为何同时使用跳跃表和字典来实现有序集合呢,而且还会保存两次???
理论上可以单独使用一种数据结构实现,但同时使用的性能会更高
- 如果我们只使用字段保存,保留了以0(1)时间复杂度查询成员的优点,但是zrank,zrange等排序命令,则需要再次进行排序,完成排序至少0(NlogN)。
- 如果我们只使用跳跃表实现有序集合,排序可以很快完成,但是查找又从0(1)上升到0(lonN)
- 最终字典和跳跃表会共享元素成员和分值,不会造成任何内存浪费
类型检查与多态命令
llen命令:确保执行命令的是列表键,而且要好根据值对象的编码,使用正确的实现
- 类型检查:为了确保只有指定类型的键可以执行某种特定操作,根据值对象类型进行检查
- 多态命令:为了同一条命令可以处理不同值类型,可以根据值对象编码使用正确的实现
内存回收和共享
内存回收
c语言不具备自动内存回收功能。
解决:redis使用引用计数实现了内存自动回收机制。
- 创建新对象时,引用计数初始化为1
- 被调用时自增1
- 不被使用时自减1
- 当引用计数变为0时,对象占用内存将被释放
内存共享
引用计数器还能实现对象共享功能。
redis会共享值为0到9999的字符串对象