
String#
实现#
字符串对象的内部编码(encoding)有 3 种 :int、raw和 embstr。
- INT编码:这个很好理解,就是存一个整型,可以用long表示的整数就以这种编码存储
- EMBSTR编码:如果字符串小于等于阈值字节,使用EMBSTR编码
- RAW编码:字符串大于阈值字节,则用RAW编码
EMBSTR 和 RAW 都是由 redisObject 和 SDS 两个结构组成,它们的差异在于,EMBSTR 下redisObject 和 SDS 是连续的内存,RAW 编码下 redisObject 和 SDS 的内存是分开的。
EMBSTR优点是redisObject和SDS两个结构可以一次性分配空间,缺点在于如果重新分配空间,整体都需要再分配,所以EMBSTR设计为只读,任何写操作之后EMBSTR都会变成RAW,理念是发生过修改的字符串通常会认为是易变的。


- embstr编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次;
- 释放 embstr 编码的字符串对象同样只需要调用一次内存释放函数;
- 因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能。
embstr 也有缺点的: 如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。
命令#
> SET key value
> SETNX key value
> GET key
> MGET key [key ...]
> EXISTS key
> STRLEN key
> DEL key
> MSET k1 v1 k2 v2
> INCR key
> DECR key
> EXPIRE
> TTL
应用#
缓存对象:SET user:1 '{"name":"xiaolin", "age":18}' 常规计数:INCR aritcle:readcount:1001
分布式锁:`SET lock_key unique_value NX PX 10000
共享session:分布式服务器使用同一个session
Hash#
实现#
3. 压缩列表 Zip List 4. 哈希表 Hashtable Hash底层有两种编码结构,一个是压缩列表,一个是HASHTABLE。同时满足以下两个条件,用压缩列表:
- Hash对象保存的所有值和键的长度都小于64字节;
- Hash对象元素个数少于512个。 两个条件任何一条不满足,编码结构就用HASHTABLE。
命令#

应用#
List#
实现#
2. Linked List 3. 压缩列表 Zip List 6. 快表 Redis 3.2 之前: Zip List(数组) 1. 列表对象保存的所有字符串对象长度都小于64字节; 2. 列表对象元素个数少于512个 Linked LIst:其余情况 Redis 3.2 之后: List 底层统一变成 quicklist
命令#

应用#
Set#
内部实现#
Set 类型的底层数据结构是由哈希表或整数集合实现的:
- 如果集合中的元素都是整数且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构;
- 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。
常用命令#

应用场景#
数据去重和保证唯一性
- 点赞
- 共同关注:SUNION
- 抽奖活动:
- SRANDMEMBER
- SPOP
ZSet#
原理#
3. 压缩列表 Zip List 7. 跳表 + 4. 哈希表 Hashtable
Zset 类型的底层数据结构是由压缩列表或跳表实现的:
- 如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;
- 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;
命令#

应用场景#
排行榜 电话、姓名排序
2. 底层数据结构#

1. SDS 动态字符串#
缺点:
- 每次计算字符串长度的复杂度为O(N);
- 对字符串进行追加,需要重新分配内存;
- 非二进制安全。
改进
- len,记录了字符串长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
- alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出的问题。
- flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
- buf[],字节数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。
2. Linked List#

3. 压缩列表 Zip List#

zlbytes,记录整个压缩列表占用对内存字节数; zltail,记录压缩列表「尾部」节点距离起始地址有多少字节,也就是列表尾的偏移量; zllen,记录压缩列表包含的节点数量; zlend,标记压缩列表的结束点,固定值 0xFF(十进制255)。
prevlen,记录了「前一个节点」的长度,目的是为了实现从后向前遍历; encoding,记录了当前节点实际数据的「类型和长度」,类型主要有两种:字符串和整数。 data,记录了当前节点的实际数据,类型和长度都由 encoding 决定; 连锁更新
- 空间扩展操作也就是重新分配内存,因此连锁更新一旦发生,就会导致压缩列表占用的内存空间要多次重新分配,这就会直接影响到压缩列表的访问性能。
- 所以说,虽然压缩列表紧凑型的内存布局能节省内存开销,但是如果保存的元素数量增加了,或是元素变大了,会导致内存重新分配,最糟糕的是会有「连锁更新」的问题。
4. 哈希表 Hashtable#

- 链式哈希
- reHash:创建两个哈希表,拷贝
- 渐进式再哈希:在 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外,还会顺序将「哈希表 1 」中索引位置上的所有 key-value 迁移到「哈希表 2」 上;新表大小为第一个大于等于2倍used的2次方幂。 再哈希时间
- 当负载因子大于等于 1 ,并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令,也就是没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作。
- 当负载因子大于等于 5 时,此时说明哈希冲突非常严重了,不管有没有有在执行 RDB 快照或 AOF 重写,都会强制进行 rehash 操作。 缩容
- 当负载因子小于0.1,即负载率小于10%,此时进行缩容,新表大小为第一个大于等于原表used的2次方幂。当然,如果有BGSAVE或BGREWRITEAOF这两个复制命令,缩容也会受影响,不会进行。
5. 整数集合 IntSet#
typedef struct intset {
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组(排序)
int8_t contents[];
} intset;
整数集合升级 INTSET_ENC_INT16到INTSET_ENC_INT32 为了保证每个元素长度相同,扩容
6. 快表#

7. 跳表#

8. ListPack#
ziplist的更新
