目录
概述
在redis中为了节约内存资源,list,zset和hash在对象满足某些条件的情况下,采用了ziplist(压缩列表进行存储)。如下所示:
127.0.0.1:6379> zadd student 1 tom 2 lily
(integer) 2
127.0.0.1:6379> debug object student
Value at:0x7f7fb826dc90 refcount:1 encoding:ziplist serializedlength:27 lru:11343959 lru_seconds_idle:14
127.0.0.1:6379> hmset books python ok java nice
OK
127.0.0.1:6379> debug object books
Value at:0x7f7fb826dc80 refcount:1 encoding:ziplist serializedlength:36 lru:11344033 lru_seconds_idle:5
接下来我们简单了解下压缩列表
压缩列表
定义
压缩列表是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构
组成结构
#内部结构图
struct ziplist<T>{
// 压缩列表占用字节数
int32 zlbytes;
//最后一个元素距离起始位置的偏移量(快速定位最后一个节点)
int32 zltail_offset;
// 元素个数
int16 zllength;
//数据 依次紧凑存储
T[] entries;
// 标志压缩列表结束 值恒为 0xFF
int8 zlend;
}
# entry结构
struct entry{
//前一个entry长度(当小于254一个字节,当大于254则为5个字节,第一个字节为 0xFE 剩下四个表示长度)
int<var> prevlen;
//编码
int<var> encoding;
//元素内容
optional byte[] content;
}
为了节约存储空间,redis对encoding进行优化设计,通过前缀来进行区分内容,可以去redis设计与实现中压缩列表编码部分进行了解
操作元素
当新增一个元素的时候可能在原有地址进行扩展,也可能进行重新分配新内存,将之前数据拷贝到新的内存空间中。在上面压缩列表的结构中,有一个prevlen字段(保存上一个entry长度),当数据进行进行增删改的时候可能会引起字节大小的变化,导致级联更新
级联更新
当前面的一个entry数据进行改变的时候,假设原来是小于254字节现在更改后就大于254字节,后面保存前面长度的字段prevlen由一个字节变为5个字节,那么后面对应所有的entry需要对应的进行更新。
整数集合
定义
整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16
、 int32
或者 int64
的整数值, 并且保证集合中不会出现重复元素
组成结构
struct intset<T>{
//编码
int32 encoding;
//元素个数
int32 length;
//数据
int<T> contents
}
升级
每当将一个新元素添加到整数集合里面, 并且新元素的类型比整数集合现有所有元素的类型都要长时(假设数据存储的是int32的数据,现在来了一个int64的), 整数集合需要先进行升级, 然后才能将新元素添加到整数集合里面。(注意:只能升级不能降级)
升级整数集合并添加新元素共分为三步进行:
- 根据新元素的类型, 扩展整数集合底层数组的空间大小, 并为新元素分配空间。
- 将底层数组现有的所有元素都转换成与新元素相同的类型, 并将类型转换后的元素放置到正确的位上, 而且在放置元素的过程中, 需要继续维持底层数组的有序性质不变。
- 将新元素添加到底层数组里面
升级之后新元素的摆放位置
因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大, 所以这个新元素的值要么就大于所有现有元素, 要么就小于所有现有元素:
- 在新元素小于所有现有元素的情况下, 新元素会被放置在底层数组的最开头(
0
); -
在新元素大于所有现有元素的情况下, 新元素会被放置在底层数组的最末尾(
length-1
)
升级的好处
整数集合的升级策略有两个好处, 一个是提升整数集合的灵活性, 另一个是尽可能地节约内存。
(紧凑列表)listpack
组成结构
//内部结构
struct listpack<T>{
//占用总字节数
int32 total_bytes;
//元素个数
int16 size;
//数据
T[] entries;
//标志压缩列表结束 值恒为 0xFF
int8 end;
}
//entry
struct lpentry{
//编码
int<var> encoding;
//数据
optional byte[] content;
//长度
int<var> length;
}
紧凑列表与压缩列表的区别
- 紧凑列表没有zltail_offset字段,最后一个元素位置可以通过total_bytes和最后一个长度字段计算出来
- 紧凑列表中每个entry中保存是当前entry长度且放在尾部,而压缩列表中则保存的是上一个entry长度
- 紧凑列表没有级联更新问题,这个是因为第2点的原因
本文来自于redis深度历险,redis设计与实现,redis官方文档等总结出来的,有什么不足请谅解,最后还是大家有空去去读下钱文品的redis深度历险这本书很给力的。