一.实现原理和应用场景
Redis支持多种数据结构,每种数据结构都有其独特的用途和实现原理。以下是Redis中常见的数据结构及其实现原理:
-
字符串(String):
- 实现原理: Redis的字符串是动态字符串,底层通过SDS(Simple Dynamic String)实现。SDS允许字符串长度动态变化,提高了字符串的效率。
- 应用场景:存储简单的键值对数据,缓存常用数据,计数器的实现。
-
哈希表(Hash):
- 实现原理: Redis的哈希表是一个键值对集合,底层使用字典实现。字典采用哈希表作为底层结构,通过哈希算法快速定位和查找键值对。
- 应用场景:存储和查询对象的多个字段,表示对象,如用户信息、商品信息。
-
列表(List):
- 实现原理: Redis的列表是一个双向链表,支持在两端进行快速的插入和删除操作。双向链表提供了对列表两端的高效操作。
- 应用场景:实现消息队列,支持先进先出的操作,存储有序的数据集合。
-
集合(Set):
- 实现原理: Redis的集合是一个无序、唯一元素的集合。底层通过哈希表实现,确保元素的唯一性,并提供高效的添加、删除、查找操作。
- 应用场景:判断元素是否存在,存储不重复的数据集合。
-
有序集合(Sorted Set):
- 实现原理: 有序集合在集合的基础上,为每个元素关联一个分数(score),通过分数来排序。底层同样使用哈希表实现,但增加了一个跳跃表(Skip List)用于支持有序性操作。
- 应用场景:排行榜的实现,按分数排序,通过分数范围获取元素。
-
位图(Bitmap):
- 实现原理: Redis的位图是一种特殊的字符串,它允许对位进行快速的设置、清除、查询等位运算操作。底层使用字节数组实现(SDS)。
- 应用场景:记录用户的行为,如签到、访问等,存储和操作大规模的二进制数据。
-
HyperLogLog:
- 实现原理: HyperLogLog用于估计一个集合的基数(不重复元素的个数),它通过概率统计算法实现。底层采用位图和哈希函数的组合,通过对哈希值进行位运算来统计集合中的唯一元素数量。
- 应用场景:估计集合的基数,用于统计UV(独立访客),统计大规模数据集合的不同元素数量。
-
地理空间索引(Geospatial Index):
- 实现原理: 用于存储地理位置信息的数据结构,Redis通过将地理位置信息映射为二维坐标,在底层使用了基于Z-Order Curve的空间索引结构。
- 应用场景:存储和查询地理位置信息,实现附近位置的搜索。
-
发布/订阅(Pub/Sub):
- 实现原理: 用于消息发布和订阅的模式,底层通过消息队列实现。Redis通过维护订阅者列表和消息队列,实现了高效的消息发布和传递。
- 应用场景:实现消息的发布和订阅机制,处理实时通信、事件通知等。
-
流(Stream):
- 实现原理: Redis的流数据结构用于持久化的日志数据,类似于消息队列。它通过双向链表和哈希表的组合实现,并支持多个消费者、消费者组、消息ID等特性。
- 应用场景:日志数据的存储和查询,实现事件溯源,保留事件的历史记录。
二.底层实现原理
SDS
在Redis中,SDS(Simple Dynamic String)是一种动态字符串的实现方式。SDS是为了解决C语言中传统的字符串表示方法的一些缺陷而设计的。
传统的C语言字符串使用字符数组来表示,但存在以下问题:
-
长度不确定: 传统字符串以
\0
作为字符串结尾,计算字符串长度需要遍历整个字符串,导致获取长度的操作时间复杂度为O(N)。 -
无法保存二进制数据: 由于
\0
的存在,传统字符串不能包含空字符,因此无法直接保存二进制数据。
SDS的设计解决了上述问题:
-
长度信息: SDS在字符串的前面增加了一个长度信息,记录了字符串的实际长度。这使得获取字符串长度的操作变为O(1)的时间复杂度。
-
二进制安全: SDS可以保存任意二进制数据,不受
\0
的限制,因此可以处理任意字节的数据。
SDS的结构如下:
struct sdshdr {
// 记录buf数组中已使用的字节数
int len;
// 记录buf数组中未使用的字节数
int free;
// 字节数组,用于保存字符串数据
char buf[];
};
字典
在Redis中,字典(Dictionary)是一种用于存储键值对的数据结构,也被称为哈希表。字典是实现Redis数据库中的键值存储的核心数据结构之一。
字典的特点:
-
高效查找: 字典采用哈希表的方式存储键值对,使得查找某个键对应的值的速度非常快,平均时间复杂度为O(1)。
-
动态扩容: 当字典中的键值对数量增加,导致哈希冲突增多时,字典会进行动态扩容,重新分配内存空间,以保证字典的高效性能。
-
灵活性: 字典可以存储不同类型的键值对,支持字符串、整数等数据类型。
-
无序性: 字典中的键值对没有固定的顺序,不像列表那样有序。
字典的结构:
typedef struct dictEntry {
// 键
void *key;
// 值
void *val;
// 指向下一个节点,解决哈希冲突时使用
struct dictEntry *next;
} dictEntry;
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引
unsigned long sizemask;
// 哈希表已有节点数量
unsigned long used;
} dictht;
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表(两个,用于实现渐进式 rehash)
dictht ht[2];
// 记录rehash进度,-1表示没有进行rehash
int rehashidx;
} dict;
-
dictEntry
:字典中的每个节点,包含键、值以及指向下一个节点的指针。 -
dictht
:哈希表结构,包含哈希表数组、大小、大小掩码以及已使用的节点数量。 -
dict
:字典结构,包含字典类型、私有数据、两个哈希表以及rehash信息。
字典采用开链法解决哈希冲突,即将具有相同哈希值的节点串成一个链表。在字典的实现中,还支持渐进式 rehash,即在字典运行过程中,通过逐步将旧哈希表中的键值对搬移到新哈希表中,以减小一次性的内存消耗。
双链表
在Redis中,双链表(Doubly Linked List)是一种常用的数据结构,用于存储有序的、双向链接的元素集合。Redis中的双链表被广泛应用在多个场景,包括列表、阻塞队列等。
双链表的特点:
-
双向链接: 每个节点都有指向前一个节点和后一个节点的指针,使得在任意节点都能方便地进行前后移动。
-
有序性: 节点的顺序是由前一个节点和后一个节点的指针决定的,保持了元素的顺序。
-
插入和删除高效: 由于节点包含了前一个节点和后一个节点的指针,插入和删除元素的操作非常高效。
Redis中的双链表结构如下:
typedef struct listNode {
// 前一个节点
struct listNode *prev;
// 后一个节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
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;
-
listNode
:双链表中的每个节点,包含指向前一个节点和后一个节点的指针,以及节点的值。 -
list
:双链表结构,包含头节点、尾节点、节点值的复制、释放和对比函数,以及节点数量。
在Redis中,双链表广泛用于实现列表(List)数据类型,也被用于其他一些场景,例如发布与订阅、慢查询日志等。其高效的插入、删除操作和有序性质使得它适用于需要频繁修改和保持有序的数据集合。
跳跃表
Redis中的跳跃表(Skip List)是一种有序的数据结构,用于实现有序集合(Sorted Set)。跳跃表通过多层索引来加速查找操作,是一种高效的动态数据结构。
跳跃表的特点:
-
有序性: 跳跃表中的元素是有序排列的,可以在O(log N)的时间复杂度内进行查找、插入和删除操作。
-
多层索引: 跳跃表包含多层索引,每一层都是原始链表的一个子集。通过这些索引,可以快速定位到目标元素,减少查找的时间复杂度。
-
平衡性: 跳跃表通过随机生成层级索引的方式保持平衡,使得在大部分情况下查找的时间复杂度保持在O(log N)。
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;
} zskiplist;
-
zskiplistNode
:跳跃表中的每个节点,包含成员对象、分值、后退指针和多层索引。 -
zskiplist
:跳跃表结构,包含头结点、尾节点、节点数量和层数。
跳跃表通过多层索引实现了在有序集合中高效地进行查找、插入和删除操作。在Redis中,有序集合数据类型的底层实现就采用了跳跃表,用于存储有序集合的成员和分值。