Redis 5 设计与源码分析
Redis 5.0 新特性
- 新增Streams数据类型,这是
Redis 5.0
最重要的改进之一。可以把Streams当作消息队列。 - 新的模块API、定时器、集群及字典。
- RDB中持久化存储LFU和LRU的信息。
- 将集群管理功能完全用C语言集成到redis-cli中,Redis 3.x 和 Redis4.x 的集群管理是通过Ruby脚本实现的。
- 有序集合新增命令
ZPOPMIN
/ZPOPMAX
。 - 改进
HyperLogLog
的实现。 - 新增Client Unblock和Client ID。
- 新增
LOLWUT
命令。 - Redis主从复制中的从不再称为
Slave
,改称Replicas
。 - Redis 5.0引入动态哈希,以平衡CPU的使用率和相应性能,可以通过配置文件进行配置。Redis 5.0默认使用动态哈希。
- Redis核心代码进行了部分重构和优化。
简单动态字符串
(1) 长度小于 32 的短字符串
struct __attribute__ ((__packed__))sdshdr5 {
unsigned char flags; // 低 3 位存储类型,高 5 位存储长度
char buf[]; // 柔性数组
}
结构如下:
(2) 长度大于 31 的字符串
此处仅展示一个示例:
struct __attribute__ ((__packed__))sdshdr8 {
uint8_t len; // 已使用长度
uint8_t alloc; // 已分配的字节总长度
unsigned char flags; // 低 3 位存储类型
char buf[]; // 柔性数组
}
SDS
读操作的复杂度多为O(1),直接读取成员变量;涉及修改的写操作,则可能会触发扩容。
跳跃表
对于有序集合的底层实现,我们可以使用数组、链表、平衡树等结构。数组不便于元素的插入和删除;链表的查询效率低,需要遍历所有元素;平衡树或者红黑树等结构虽然效率高但实现复杂。Redis采用了一种新型的数据结构——跳跃表。跳跃表的效率堪比红黑树,然而其实现却远比红黑树简单。
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zkiplist;
在Redis中,跳跃表主要应用于有序集合的底层实现(有序集合的另一种实现方式为压缩列表)。zset
插入第一个元素时,会判断下面两种条件:
zset-max-ziplist-entries
的值是否等于0;zset-max-ziplist-value
小于要插入元素的字符串长度。
满足任一条件Redis就会采用跳跃表作为底层实现,否则采用压缩列表作为底层实现方式。一般情况下,不会将zset-max-ziplist-entries
配置成0,元素的字符串长度也不会太长,所以在创建有序集合时,默认使用压缩列表的底层实现。
zset
新插入元素时,会判断以下两种条件:
zset
中元素个数大于zset_max_ziplist_entries
;- 插入元素的字符串长度大于
zset_max_ziplist_value
。
当满足任一条件时,Redis便会将zset
的底层实现由压缩列表转为跳跃表。值得注意的是,zset
在转为跳跃表之后,即使元素被逐渐删除,也不会重新转为压缩列表。
跳跃表的原理简单,其查询、插入、删除的平均复杂度都为O(logN)。
压缩列表
压缩列表ziplist
本质上就是一个字节数组,是Redis为了节约内存而设计的一种线性数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数。
Redis的有序集合、散列和列表都直接或者间接使用了压缩列表。当有序集合或散列表的元素个数比较少,且元素都是短字符串时,Redis便使用压缩列表作为其底层数据存储结构。
元素的结构示意图:
字典
Redis自带客户端就是使用times 33
散列函数来计算字符串的Hash值,Redis服务端的Hash函数使用的是siphash
算法,主要功能与客户端Hash函数类似,其优点是针对有规律的键计算出来的Hash值也具有强随机分布性,但算法较为复杂。
整数集合
整数集合(intset
)是一个有序的、存储整型数据的结构。
127.0.0.1:6379> sadd testset 1 2 1 6
(integer) 4
127.0.0.1:6379> object encoding testset
"intset"
intset
是按从小到大有序排列的,所以通过防御性判断之后使用二分法进行元素的查找。
quicklist的实现
quicklist
是Redis底层最重要的数据结构之一,它是Redis对外提供的6种基本数据结构中List
的底层实现,在Redis 3.2版本中引入,能够在时间效率和空间效率间实现较好的折中。quicklist
由List
和ziplist
结合而成。quicklist
是一个双向链表,链表中的每个节点是一个ziplist
结构。quicklist
可以看成是用双向链表将若干小型的ziplist
连接到一起组成的一种数据结构。
Stream
Redis Stream
的结构如图所示,它主要由消息、生产者、消费者、消费组4部分组成。
xadd mystream1 * name zk age 20
mystream1
为Stream的名称;*代表由Redis自行生成消息ID;name、age为该消息的field; zk、20则为对应的field的值。
每个消息都由以下两部分组成。
- 每个消息有唯一的消息ID,消息ID严格递增。
- 消息内容由多个field-value对组成。