7.2 压缩节点连锁更新的问题
一个节点由三部分组成:记录上个节点字节数的pre_entry_len、记录内容的content以及记录当前节点属性的encoding。
由于每个节点的pre_entry_len部分会记录上一个节点占用的字节数。当content从250 -> 253字节时,会引发下一个节点的pre_entry_len更新,由此下个节点的字节数增加,从而可能引发下下一个节点的pre_entry_len更新,由此最坏情况下节点更新的复杂度为O(n),但这种可能性极小。可以忽略不计。
8.对象
redis中维护的数据都不是直接用五种数据结构存储的,而是用对象。
set的key和value都是用对象维护的
typedef struct redisObject{
// 类型 五种基本类型string, list, hash, set, zset
unsigned type:4;
// 编码 eg.整数值实现的字符串、SDS实现的字符串,即不同实现模式的基本类型
// 压缩列表实现的zset,跳表实现的zset
unsigned encoding:4;
// 指向底层实现数据结构的指针
void *ptr;
// 引用计数
int refcount;
// 记录最后一次被访问的时间
unsigned lru:32;
}
开始比较疑惑有了type:4字段还要encoding:4作甚,对于存储的值类型还是要用不同实现方法的基本类型,具体场景具体分析的方法论还是有道理的。
long double类型在redis是以字符串存储的。若用数字类型存储,可能在不同平台传输可能丢精度,用字符串保证了这种情况不会发生。
不同编码实现的基本存储结构,对应函数的实现可能不同。例如int编码的字符串和embetr、raw编码的字符串的STRLEN函数,int编码需要先将数值转成的字符串再计算字符串长度,而embetr、raw编码的字符串调用sdslen函数即可,而sdslen我们知道是O(1)的。
列表有ziplist和linkedlist两种编码
最大的不同之处在于ziplist直接存的字符串,而linkedlist存的是StringObject对象。
使用场景:ziplist用于短小的list存储。具体使用条件——存储的字符串大小 < 64字节 且 元素数量小于512。
看了下ziplist编码使用的list的“LSET”命令,是先删除节点再插入节点,而linkedlist是直接覆盖,这点符合直觉的。为什么ziplist要这样做,考虑要保持内存连续性,先删除再插入可以触发内存分配重排机制。我也不是很清楚为什么直接赋值不行,可能偏低层吧,而且通常覆盖操作是不会完全删除原来数据,比如"123" 改成 "1",考虑到性能,让我实现的话会记录下新字符串的长度和字符串首地址,旧数据不会马上从内存中删除,而是积攒一部分数据后再集体删除。(以上都是我口胡的)
哈希对象的编码有ziplist和hashtable,hashtable是用字典实现的。
ziplist编码实现的哈希,其实就是暴力找呗。适用场景是元素数量小于512,这也符合直觉,没啥好说的。
hashtable编码也不过赘述了,前面讲字典时具体介绍了。
集合对象编码有intset或者hashtbale(跳过)
有序集合对象编码ziplist和skiplist,提一下zset是有一个字段专门存储k - v映射,如下图。
内存回收 (引用计数)
对象共享(类似共享内存,两个相同值就值向同一片内存)。书中有说Redis不共享包含字符串的对象,是因为验证(即Object.equal(a,b))包含多个字符串的对象的复杂度是O(n^2),我理解可以给字符串对象加个哈希值不就能降到O(n)了。
对象的空转时间(空闲时间) = 当前时间 - 上次访问时间
二. 单机数据库的实现
9. 5 过期键删除策略——定时删除、惰性删除、定期删除
ps:感觉都是之前背过的八股(
定时删除:key创建时同时创建一个定时器,到过期时间自动删除
缺点:高并发时对cpu占用大,每个key多要单独删除
惰性删除:调用key时检查key是否过期
缺点:对内存不友好,过期key存于内存中
定期删除:每隔一段时间,批量删除过期key
缺点:上面两者的中间情况,具体要看定期删除的策略——参数为时间间隔、删除key数量限制
10.RDB持久化
RDB文件是在redis服务器启动时自动载入的,并没有直接载入的命令。
SAVE和BGSAVE命令可用来保存当前数据。其中SAVE会阻塞主进程,而BGSAVE是fork子进程进行保存
AOF开启的情况下,会优先使用AOF来还原redis。
RDB文件结构
REDIS:即保存的是"REDIS"字符的二进制,作为RDB文件的标识
db_version:版本号
databases: 存的数据库数据
EOF:数据结尾的标识,长度1字节
check_sum:校验和