Redis数据结构
一、string
三种编码方式:int、embstr、raw
- EMBSTR:数据连续存储
- RAW: 数据链式存储
SDS
自定义数据结构,区别于C语言中的char[]。
优点
- 记录长度,时间复杂度为O(1)
- 二进制安全
- 增加空余空间(alloc-len),为后续追加数据留余地;
alloc规则:min(2len,len+1M)
二、List
6之前最大元素个数:2^32-1
6及6之后:2^64-1
底层:ZIPLIST、linkedlist
-
ziplist:list中的字符串紧凑存在一起
-
linkedlist:list中的字符串对象以链表形式连接
2.1 quicklist
ziplist+linkedlist:由多个ziplist使用链式存储的方式连接
redis7之后ziplist被listpack取代(连锁更新问题)
c
// from Redis 5.0.5
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned long len; // 记录了表的长度
} list;
2.2 压缩列表

zlbytes:表示整个压缩列表所占的空间
zltail:相对于起始指针的偏移量,也就是可以定位到最后一个节点
zllen:表示有多少个数据节点
zlend:表示ziplist的结束
entry结构
结构: <prevlen><encoding><entry-data>
- prevlen: 表示上个节点的数据长度,小于254,则使用一个字节,大于等于254则使用5字节,这5个字节中,第一个字节是11111110(254),后4个字节表示真实的长度
为什么是5字节而不是2字节:是由于redis设计是这样的,意思是要么就表示长度小的,要么就表示长度大的,中间长度的就使用大的来表示。
- encoding:编码类型。包含了entry的长度信息
- entry-data:实际的数据
操作复杂度:
-
查:
- 查总数:zllen中有记录(但是如果数据超过65535,zllen会变成0,这个时候需要遍历)
- 查数据:遍历
-
增删:
增加节点的长度之后,后面的节点的prevlen需要更新,可能需要由1字节更新为5字节,后面的后面的节点也需要更新,所以需要进行多次更新。(连锁更新)
删除元素之后,prevlist不会由5字节变成1字节
listpack
结构: <encoding-type><element-data><element-tot-len>
-
encoding-type:编码
-
element-data:数据
-
element-tot-len:整个节点除去element-tot-len的长度,每个字节的第一位0表示停止,1表示继续
例如:
element-tot-len: 0000 0001 1000 0100
下一个元素会从上个元素的element-tot-len的最后一位开始遍历,直到遍历到第一个字节为0位置,
上述例子遍历到 000 0001 000 0100 => 132 表示遍历结束位置再往前132字节就可以找到上一个节点的起始位置
三、Set
编码方式:
-
intset:查询时使用二分查找,有序,只能存整数,如果存入非整数编码会变成hashtable
-
hashtable
四、hash
编码格式
- 压缩列表
- hashtable
hashtable底层

和java中hashmap差不多
table:可以看成一个数组,每个数组都是存储entry的结构
size:表示table的大小
used:表示已有节点数量
hash冲突使用拉链法(同hashmap,使用头插法)
-
渐进式扩容:
rehashidx标志位 -1 -> 0 表示开始扩容 (rehashidex是一个指针,从头开始遍历,迁移元素),和concurrenthashmap类似。
-
负载因子:
负载因子 >= 1 且没有在执行写RDB或AOF (bgsave 或 bgrewriteraof)
负载因子 >= 5 强制扩容
负载因子 < 0.1 缩容 也是使用渐进式
java中hashmap和arraylist不支持缩容
五、Zset
编码:
ziplist/skiplist+hashtable
-
zpilist
-
跳表+hashtable
查询节点总数复杂度:
ziplist和跳表都是O(1)
为什么zset使用跳表外还同时使用hashtable
当ZSet要根据成员来查找分值的时候只用使用字典来查找,时间复杂度为O(1)。而当ZSet要执行范围操作时,比如ZRANK、ZRANGE等命令时,将使用原本就有序的跳跃表来实现。
跳表底层
是否能增加一层的概率为25%,最大层数5.0是64,7.0是32
操作复杂度:O(logn)
注:上图是逻辑上的结构
实际结构没有这么多的节点,而是在每个节点中维护一个数组,记录每个节点的后继节点,对应于图中下方部分的结构。
跳表定义的源码:
c
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
// 节点数量
unsigned long length;
int level;
} zskiplist;
c
// Redis 7.0.8
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
六、 对象删除
设置过期时间:set k v ex 过期时间
删除:
- 定时删除
- 惰性删除
- 定期删除
使用:定期删除+惰性删除