深入理解 Redis 数据结构

一、数据结构

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,详细步骤如下:

  1. 为ht[1] 分配空间,将rehashidx 置为0
  2. 将ht[0]的键值对逐一rehash到ht[1],每rehash一对,rehashidx就加一
  3. rehash过程中的添加会直接添加到ht[1]上,查找会先在ht[0]查,查不到再到ht[1]查
  4. 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条件不成立时

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值