本文是《redis设计与实现–黄健宏》一书的读后感和笔记。
SDS(simple dynamic string)
简单动态字符串: redis自定义的字符串结构:
1 | struct sdshdr { |
为什么不使用C语言的字符串字面量的原因:
- 获取字符串长度复杂度为O(1), 而不是O(N)
- 杜绝缓冲区溢出: 增加字符串内容而忘记分配合适的长度造成覆盖等
- 减少修改字符串的内存重分配: 主要采用了2种策略
- 空间预分配: 低于1MB长度时,扩展会翻倍,超过1MB时每次多分配1MB
- 惰性空间释放: 缩短字符串时并不内存分配,只是修改free的长度
- 二进制安全: 可以保存除文本外,图像、视频、音频等2进制信息,因为不以
\0
来判断结尾,而是len - 兼容部分C语言库函数: sds依然保留
\0
结尾,所以可以重复使用部分字符串函数。
链表
list的数据结构
单个节点: 是一个双向链表
1 | typedef struct listNode { |
list结构:
1 | typedef struct list { |
dict
保存键值对
数据结构
1 | typedef struct dictht { |
下面是dictEntry: 每个entry后面可以跟一串entry, 用于到hash出的索引冲突时。
1 | typedef struct dictEntry { |
下面才是字典的最后封装:
1 | typedef struct dict { |
- hash table为什么需要2个?
因为ht[1]是在rehash时使用的,后面会说 - rehashidx的作用是啥?
是一个计数器,标记rehash的进度和是否完成。 - 绘制出dict的数据结构图
hash算法
1 | hash = dict->type->hashFunction(key); |
这个索引值就是dictEntry数组里的下标。
hash算法: MurmurHash
冲突解决
采用链地址法: 采用前插法,在dictEntry形成链表。
rehash
- 为什么需要?
因为一段时间后hash表会变得要么很大,要么很少,为了维持一个负载均衡因子,会有2种可能:扩展和收缩。 - rehash的步骤?
- 分配空间给ht[1]
1. 扩展: newSize >= used*2
2. 收缩: newSize >= used - 转移,这是一个渐近的过程,并不是一下就过去了,后面会说
- 释放ht[0], 交换ht[1]为ht[0]
- 什么时候hash表会扩展?
- 负载因子: load_factor = ht[0].used / ht[0].size
- 当没有BGSAVE或BGREWRITEAOF在执行且负载因子>=1, 不知什么时候会大于1?
- 当上述命令在执行,且负载因子>=5,执行时负载因子会变大,因为copy on write技术需要节约内存
- 什么时候收缩?
当负载因子 < 0.1
渐近式rehash
- why?
因为当键值对很大时,一次性转移会暂停服务,需要慢慢来 - 步骤
- 这个过程会同时用到ht[0],ht[1]
- 初始rehashidx设为0
- 每移动一个key-value,rehashidx++
- 直到完成,rehashidx重置为-1
- 在rehash阶段,CRUD怎么办?
修改会只在ht[1]上进行, 查询会从ht[0]到ht[1]
跳跃表
- 什么是跳跃表?解释其原理。
见: skip list理解与java实现 - 在redis中哪里用到了跳跃表?
2个地方:1.有序集合集(sorted set) 2.集群节点中用作内部数据结构
对象
实际上,redis的键值都是对象。
1 | typedef struct redisObject { |
type
有: string, list, hash, set, zset
, 通过下面的命令可以查看:
1 | SET msg "hello world" |
encoding
表示使用哪种数据结构,有:int,embstr,raw,hashtable,linkedlist,ziplist,intset,skiplist
,
可以使用OBJECT ENCODING xxx
查看:
1 | SET msg "hello string" |
一个数据结构可以有多种编码:
- string: int/raw/embstr
- list: ziplist/linkedlist
- hash: ziplist/hashtable
- set: intset/hashtable
- zset: ziplist/skiplist
问:
- 在什么条件由一种结构换到另一种?
- 有什么是embstr呢? 见下面。
string对象
字符串对象的编码可以是: int, raw, embstr
- long能存下的数字肯定是int编码
- 小于等于39字节的恩字符串是 embstr
- 大于39字节的是 raw,用sds存
embstr与raw的区别?
embstr只分配一次内存就将redisObject和sds存好,也只需要释放一次,而raw是2次。
long double类型的浮点数也是存的字符串对象, 在需要运算时才转换。
编码的转换
编码在一定条件是会转换的,比如:
- int会转为embstr或raw, 在整数中加入字符串时。
- embstr没有修改函数,要修改时也会转成raw。