一、数据结构
1.1 简单动态字符串(simple dynamic string , SDS)
redis 没有使用C自带的字符串(以空字符结尾的字符串数组)实现,而是自己定义SDS结构来存储字符串,SDS结构如下:
修改SDS时涉及到以下两点:
1. 空间预分配:
用于优化字符串增长操作:当SDS需要进行空间扩展时,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的空闲空间。好处:下次扩展时,如果所需空间小于空闲空间,则不需进行内存扩展,而是直接进行修改。
- SDS修改后:len < 1MB,那么 free 的大小也为 len
- SDS修改后:len >= 1MB,那么free 的大小为 1MB
2. 惰性释放:
用于优化字符串缩短操作:当SDS保存的字符串缩短是,程序不立即进行内存回收来释放多出来的空间,而是使用free属性将这些空间记录起来,并等待将来使用。
1.2 链表
redis链表是无环的,对链表的访问以null为终点;具有head、tail、len 所以能快速获取链表的头尾节点及长度。结构如下:
1.3 字典
字典中的每个键都是唯一的,数据结构如下:
随着操作不断进行,哈希表保存的键值对会逐渐地增多或减少,为了让负载因子(load_facotr=ht[0].used / ht[1].size)维持在一个合理的范围内,需要对哈希表的大小进行相应的扩展或者收缩,即rehash。
当 ( load_factor >= 1 && 没有在执行bgsave 或 bgrewriteaof ) || (正在执行bgsave 或 bgrewriteaof && load_factor >= 5) 时,执行扩展。ht[1] 的大小为:>= ht[0].used *2 且是 2的 n 次方幂。如 ht[0].used =3,则 ht[1]的大小为:3*2=6, 而6不是2的 n 次方幂,所以取8。
当 load_factor < 0.1 时执行收缩。ht[1] 的大小为:>= ht[0].used 且是 2的 n 次方幂。如 ht[0].used =3,则 ht[1]的大小为:3, 而3不是2的 n 次方幂,所以取4。
为了避免因哈希表的长度过长,而导致在rehash时因占用太多资源而造成服务器停止服务。Redis使用渐进式rehash,详细步骤如下:
- 为ht[1] 分配空间,将rehashidx 置为0
- 将ht[0]的键值对逐一rehash到ht[1],每rehash一对,rehashidx就加一
- rehash过程中的添加会直接添加到ht[1]上,查找会先在ht[0]查,查不到再到ht[1]查
- rehash完成后将rehashidx置为-1,释放ht[0],将ht[1] 置为ht[0]
1.4 跳跃表
1.5 整数集合
如果contents数组的类型原本为INSET_ENC_INT16(表示范围:-32768 - 32467),此时若加入一个40000的数,则 redis 会自动将该数组类型自动升级为 INSET_ENC_INT32。但如果将40000移除,则redis不会重新降级为INSET_ENC_INT16。
1.6 压缩列表
由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
1.7 快速列表
Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,quicklist的每个节点都是一个ziplist。参考:http://zhangtielei.com/posts/blog-redis-quicklist.html
quicklist的结构为什么这样设计呢?总结起来,大概又是一个空间和时间的折中:
- 双向链表便于在表的两端进行push和pop操作,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
- ziplist由于是一整块连续内存,所以存储效率很高。但是,它不利于修改操作,每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝,进一步降低性能。
二、对象
Redis 使用对象来保存数据库中的键和值,每一个对象至少对应一种数据结构。
每个对象都由redisObject结构表示,该结构中有以下三个字段是跟数据保存有关的:
typedef struct redisObject{
unsigned type; //数据类型: string list hash set zset,可通过 type key 查看
unsigned encoding; // 数据结构类型:int embstr raw ht linkedlist ziplist intset skiplist,可通过 object encoding key 查看
void *ptr; //指向底层实现数据结构的指针
//...
} robj;
下面讲解每一种数据类型对应的编码(数据结构):
2.1 字符串 string
编码类型 | 说明 |
int | 能用long表示的数值 |
embstr | 长度<=32 |
raw | 长度>32 |
raw编码的redisObject和sdshdr是分开存储的,所以需要进行2次内存分配;而embstr的redisObject和sdshdr是存储在一块连续的空间里,所以只需一次内存分配。
2.2 列表对象 list
编码类型 | 说明 |
ziplist | 所有元素的长度<64且元素的数量小于512 |
linkedlist | ziplist条件不成立时 |