引言
只有弄明白Redis数据结构,才能理解它如此快速的原因,并不只是它存储于内存,本篇文章将拆开Redis数据结构分析它高效的原因
字符串(String)
基本概念:字符串是 Redis 中最基本的数据结构,可以存储任何形式的字符串,包括文本、二进制数据等,一个字符串的最大长度可达 512MB。
底层代码:
struct sdshdr {// 记录 buf 数组中已使用字节的数量,等于 SDS 所保存字符串的长度int len;// 记录 buf 数组中未使用字节的数量int free;// 字节数组,用于保存字符串char buf[];
};
- len:该字段记录了当前 SDS 中存储的字符串的实际长度。通过这个字段,获取字符串长度的操作时间复杂度为 O (1),而在传统 C 字符串中,需要遍历整个字符串来获取长度,时间复杂度为 O (n)。
- free:它表示 buf 数组中尚未使用的字节数量。这个字段的存在使得 SDS 在进行字符串修改时,可以预先分配足够的空间,减少内存重新分配的次数。
- buf:这是一个字符数组,用于实际存储字符串的内容。在数组的末尾,会以 '\0' 结尾,这样可以兼容部分 C 字符串函数。
总结以上:获取字符串长度时间复杂度O(1),减少内存分配次数,兼容C语言函数
应用场景
缓存数据:可缓存网页内容、数据库查询结果等,将网页的 HTML 内容以字符串形式存储,键为网页的 URL。
计数器:用于统计网站访问量、用户点赞数等,通过incr命令记录文章阅读次数。
列表(List)
基本概念:
列表是 Redis 中的一种线性数据结构,支持在头部或尾部插入和删除元素。列表可以存储多个字符串元素,元素可以重复,最大长度为 2^32 - 1。
底层代码:
Redis 列表的底层实现有两种:
-
压缩列表(ziplist):用于存储小列表,节省内存。
-
双向链表(linkedlist):用于存储大列表,支持快速的头尾操作。
// 压缩列表结构(ziplist) struct ziplist {unsigned char *zlbytes; // 整个压缩列表占用的字节数unsigned char *zltail; // 最后一个节点的偏移量unsigned char *zllen; // 节点数量unsigned char *zlentry; // 节点数据 };// 双向链表节点结构 struct listNode {struct listNode *prev; // 前驱节点struct listNode *next; // 后继节点void *value; // 节点值 };// 双向链表结构 struct list {listNode *head; // 链表头节点listNode *tail; // 链表尾节点unsigned long len; // 链表长度 };
特点:存储偏移量避免遍历查找末尾字节
压缩列表:内存紧凑,适合小列表,但插入和删除效率较低。
双向链表:支持 O(1) 时间复杂度的头尾插入和删除操作,适合频繁修改的场景。
应用场景:根据不同指定产生中间件
消息队列:使用 LPUSH 和 RPOP 实现队列。
最新消息列表:使用 LPUSH 和 LRANGE 获取最新消息。
栈:使用 LPUSH 和 LPOP 实现栈。
哈希(Hash)
基本概念:
哈希是 Redis 中的一种键值对集合,适合存储对象。每个哈希可以存储多个字段和值,字段和值都是字符串类型。
底层代码:
Redis 哈希的底层实现有两种:
-
压缩列表(ziplist):用于存储小哈希,节省内存。
-
字典(dict):用于存储大哈希,基于哈希表实现。
// 字典结构 struct dict {dictEntry **table; // 哈希表数组unsigned long size; // 哈希表大小unsigned long sizemask; // 哈希表大小掩码unsigned long used; // 已使用的节点数 };// 哈希表节点结构 struct dictEntry {void *key; // 键union {void *val;uint64_t u64;int64_t s64;} v; // 值struct dictEntry *next; // 指向下一个节点(解决哈希冲突) };
-
压缩列表:内存紧凑,适合小哈希。
-
sizemask(哈西表大小掩码): 是哈希表大小减 1(size - 1),用于将哈希值映射到哈希表的索引范围内。它的主要作用是:
快速计算索引:通过 hash & sizemask 的方式,将哈希值映射到哈希表的索引位置。
保证索引在合法范围内:由于 sizemask = size - 1,且 size 是 2 的幂次方,hash & sizemask 的结果一定在 [0, size-1] 范围内。
-
字典:支持 O(1) 时间复杂度的查找、插入和删除操作。
应用场景:
-
存储对象:如用户信息、商品信息。
-
字段级操作:可以直接修改某个字段,而不需要读取整个对象。
集合(Set)
基本概念:
集合是 Redis 中的一种无序且唯一的数据结构,适合存储不重复的元素。
底层代码:
Redis 集合的底层实现有两种:
-
整数集合(intset):用于存储整数集合,节省内存。
-
字典(dict):用于存储大集合,基于哈希表实现。
// 整数集合结构 struct intset {uint32_t encoding; // 编码方式(int16、int32、int64)uint32_t length; // 集合长度int8_t contents[]; // 集合内容 };// 字典结构(同上)
-
整数集合:内存紧凑,适合存储整数。
-
字典:支持 O(1) 时间复杂度的查找、插入和删除操作。
应用场景
-
去重:如存储用户 ID、标签。
-
集合运算:如交集、并集、差集。
位图(Bitmap)
基本概念:
位图是 Redis 中的一种基于字符串的数据结构,每个 bit 位表示一个状态。不同于布尔数组,它的每个位只占一个bit
底层代码:
位图底层使用字符串(SDS)存储。
// SDS 结构(同字符串)
struct sdshdr {int len; // 字符串长度int free; // 未使用字节数char buf[]; // 字节数组
};
-
极省内存:每个 bit 位存储一个状态。
-
位操作:支持高效的位运算(如 AND、OR、NOT)。
应用场景:
-
用户签到:记录用户每天的签到状态。
-
活跃用户统计:统计某段时间内的活跃用户。
位图的高效性
-
内存效率:
- 每个位只占用 1 bit,比使用布尔数组或整数数组更节省内存。
- 例如,存储 100 万个布尔值只需要 125KB 内
2. 位运算性能:
- Redis 的位操作是基于 CPU 的位运算指令实现的,性能非常高。
- 例如,
BITCOUNT
命令使用高效的算法统计 1 的数量。
3. 批量操作:
- 支持批量设置和获取位值,减少网络开销。