字符串由两个部分组成,一个是redis对象通用结构体 RedisObject
,一个是字符串特有的 SDS(Simple Dynamic String)
简单动态字符串结构体
1 | //RedisObject 16Bytes |
1 | //SDS >=3Bytes |
字符串的编码分为两种: embstr
和 raw
embstr
使用 malloc
方法申请空间, jemalloc
分配空间大小是2/4/8/16/32/64字节,因为embstr最小需要19Bytes,所以给 embstr
最小分配32字节空间,稍微长一些就是64字节空间,Redis的逻辑是当embstr空间大于64字节时,转为 raw
存储
因为字符串为了便于使用 glibc
函数处理,以及便于调试打印输出,所以在实际存储中,会在字符串数组末尾加入 NULL
字符,占用1Byte
embstr编码下,字符串的最大字符数是:64-19-1=44Bytes
raw
与embstr不同,embstr是调用一次malloc函数申请空间,raw是调用两次malloc函数,申请两块不连续的空间,使用 RedisObject
中的 *ptr
指针进行指向SDS,在字符串大于44Bytes时会使用raw格式存储
当字符串小于1MB时,扩容contents数组时加倍扩容,而大于1MB时,每次扩容多分配1MB空间
字典结构应用到的地方由很多,比如redis作为远程字典服务器本身就是一个由key和value大字典,而在zset集合中score和value值也是一个字典,以及key的过期时间存储也是由字典实现。
1 | //redisDB |
字典结构中主要是有两个hashtable
1 | //dict |
hash中的关键逻辑
O(n)
时间复杂度的操作,redis在rehash中维护两个hashtable,一个旧一个新,新增的entry会新增到新hash中,并且旧hash会逐渐在hset、hdel逻辑中主动搬运,如果没有这些操作,也会在定时任务中进行搬运。O(1)
,扩容为原数组大小的两倍,但如果在 bgsave
过程中,则会推延扩容直到元素总数达到数组长度的5倍,就会触发强制扩容bgsave
有序集合 zset
在元素较少 (<128)&(len<64)
时,会使用 ziplist
,这个数据结构没有空隙,所有元素都是紧挨着存放。
查找时间复杂度: O(n)
插入时间复杂度: O(n)
压缩链表结构
1 | struct ziplist { |
元素结构
1 | struct entry { |
ziplist关键逻辑:
254
时,使用一个字节,超出则使用5个字节在 set
集合元素都是整数且元素较少 <512
时,会使用 intset
进行存储, intset
支持16、32、64位整数,结构为紧凑型数组。
1 | struct intset<T> { |
快速列表结合了 ziplist
和 linkedlist
,在元素少时,只使用 ziplist
,元素较多时,把 ziplist
使用 双向循环指针
链接起来,成为一个由 ziplist
作为元素的 linkedlist
。
关键逻辑:
LZF算法
压缩,可以选择压缩深度,所谓的压缩深度就是从首尾算起多少个元素可以不压缩,深度为0就是不压缩,深度大于0时越大压缩的ziplist越少。list-max-ziplist-size
设定,默认为8KB数据结构 zset
是复合结构,其中的结构包含了一个 hash字典
和 一个 skiplist跳跃列表
,其中 skiplist
是比较复杂的。
1 | //zslnode 跳表节点 |
对于每一个新插入的元素,会调用一个随机算法来计算他最高处于哪个层,理论上每升一层的概率是50%,但是redis为了限制层高,把这个概率设置为了25%,也就是实际上redis中的跳表比理论的更矮。
rank
是通过存储在 forward指针中的span(跨度)属性计算的,具体是在获取节点时,获取到搜索路径,将搜索路径中的span相加,就获得了排名了