Redis(Remote Dictionary Server)是一种开源的高性能键值存储系统。
基本工作原理如下:
- 内存存储:Redis主要将数据保存在内存中,能够提供快速的读写性能。内存中的数据通过快速的读写操作来满足用户请求,而被持久化在磁盘上的数据则用于数据恢复和持久性
- 单线程模型:Redis采用单线程模型来处理客户端请求。所有的命令都是顺序执行的,每个命令都会被串行地处理。这种简单的模型减少了锁的竞争和线程切换的开销,从而提高了性能
- 数据结构支持:Redis支持多种数据结构,如字符串、哈希表、列表、集合和有序集合等。这些数据结构的支持使得Redis在处理各种数据类型时更加灵活和高效
- 持久化机制:Redis提供两种持久化机制,分别是快照(snapshotting)和日志(logging)。快照通过将数据库的状态保存到磁盘上的文件中来实现,而日志则记录了所有修改操作,通过重放这些操作来恢复数据。这样可以保证在系统故障或重启后数据的可靠性和一致性
Redis 实现的特殊数据结构
String 数据结构
在Redis内部实现中,string数据结构是最基本的数据类型之一。下面是Redis内部实现string数据结构的一些关键点:
- 数据结构:Redis的string数据结构使用简单动态字符串(Simple Dynamic String,SDS)来表示。
SDS
是一个灵活的、可自动扩容的字符串结构,它相比于传统的C语言字符串更加安全且性能更好 - 存储方式:Redis中的string数据结构通过SDS存储在内存中。SDS结构包含一个指向字符数组的指针、字符串的长度信息(
len
)和分配的空间大小(alloc
)等字段,使得Redis可以快速地进行字符串的读取和写入操作 - 常用操作:Redis提供了一系列常用的操作命令来处理string类型的数据,比如
GET
、SET
、APPEND
、INCR
等,可以用于获取、设置、追加和增减字符串的值 - 内存优化:为了减少内存占用,Redis对于较短的字符串会进行压缩存储。当字符串的长度小于一定阈值(默认为44字节)时,Redis会将SDS结构中的字符数组缩小到实际所需的长度
- 二进制安全:Redis的string数据结构是二进制安全的,即可以存储任意二进制数据,而不仅仅局限于文本,使得Redis可以用于存储图片、视频、序列化对象等多种数据类型
- 操作复杂度:对于大多数常见的操作,比如获取和设置字符串的值,Redis的string数据结构的操作复杂度为O(1),即常数时间复杂度,使得Redis在处理大量请求时能够保持高效的性能
List 数据结构
Redis内部实现的list数据结构是双向链表(doubly linked list)。在C语言中,Redis使用一个定义了节点结构和列表结构的头文件来实现这个数据结构。
节点结构(listNode)定义如下:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
每个节点包含了一个指向前一个节点的指针(prev
)、一个指向后一个节点的指针(next
)以及一个指向存储值的指针(value
)。
这个存储的值指针指向的可能是一个listpack
,即为元素节点的打包,内含有多个元素
列表结构(list)定义如下:
typedef struct list {
listNode *head;
listNode *tail;
unsigned long len;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
} list;
列表结构包含了一个指向头节点的指针(head
)、一个指向尾节点的指针(tail
)、表示列表长度的无符号长整型变量(len
),以及一些用于操作节点值的函数指针。其中,dup
函数用于复制节点值、free
函数用于释放节点值、match
函数用于比较节点值。
通过使用双向链表,Redis可以高效地进行头部和尾部的插入、删除操作,同时也能够在常数时间内根据索引位置进行访问。这使得Redis的list类型支持快速的队列操作,例如在列表的两端进行元素的推入(push
)和弹出(pop
)操作。
Dict 数据结构
Redis 内部实现的哈希数据结构 dict 是通过开放地址法的哈希表实现的,每个 dict 对象包含一个哈希表,用于存储键值对。通过哈希函数计算索引值,并使用桶和节点的结构来处理插入、查找和更新操作。这种实现方式使得 Redis 能够高效地处理大量的键值对,具有较低的时间复杂度。
当需要执行rehash
操作,即将ht[0]
中的数据全部迁移到ht[1]
中时,如果数据量较大,有上百万的键值对时,直接完整拷贝需要花费时间,导致用户请求阻塞,因此要采用渐进式rehash
的方式:每次用户访问时,都迁移用户访问的该部分少量数据,将整个迁移过程,平摊到所有的访问请求过程中。
Zskiplist 数据结构
Redis 内部实现的有序集合(Sorted Set)数据结构 zskiplist
(跳跃表)是一种基于链表的数据结构,用于存储有序的成员和对应的分值。
zskiplist 是由多层链表组成的,每一层都是一个有序的链表。每个节点包含了一个成员和一个分值,并且通过前进指针和跨越指针连接到其他节点。最底层的链表包含了所有节点,而上层的链表是下层链表的一个子集,通过跳跃指针快速跳过部分节点,减少查找的时间复杂度。
当执行查找操作时,Redis 会从最高层开始,比较节点的分值与目标分值,并根据节点的指针跳跃到下一层或下一个节点,直到找到目标节点或搜索结束。
通过使用跳跃指针和分层结构,zskiplist 能够较快地进行插入、删除和查找操作,并具有较低的时间复杂度。此外,zskiplist 中每个节点的层数是通过随机算法生成的,使得整个结构相对平衡,以提高性能。
需要注意的是,为了保证有序集合的唯一性,zskiplist 在插入和更新操作时会检查成员是否相等,并根据需要进行替换或更新。