redis单机数据库
后端
数据结构
简单动态字符串
与C字符串的区别:
- redis字符串用 len 记录字符串的实际长度,所以获取字符串长度复杂度为O(1);
- C字符串用空字符标记字符串结束,拼接函数也不检查目标字符串的空间是否足够,所以会导致内存溢出,redis字符串可以避免这个问题;
- 减少内存重分配的次数
- 内存预分配,在分配内存时,预留一些未使用的空间
- 惰性释放
- 不必受限于ASCII码,可以存储任何二进制数据
链表
节点结构,包含前驱指针、后继指针、节点数据
链表结构,包含头节点、尾节点、节点数量、具体数据类型的处理函数指针,支持多态
字典
哈希表
哈希表节点,保存了键、值和指向下一个节点的指针,通过链表解决哈希冲突
字典包含着两个哈希表、私有数据和类型特定的函数
字典在扩容和收缩过程中,为了提高性能使用渐进式rehash:
- 字典持有两个哈希表,ht[0]用于正常提供服务,ht[1]用于扩容;
- 哈希表的负载大于负载因子时扩容,小于时收缩,都将将进行rehash,设置rehashidx为0,表明开始了rehash;
- 每次进行读取或写入操作时,顺带对rehashidx位置上的元素进行rehash,然后将rehashidx增加一;
- 当ht[0]中的元素全部迁移到ht[1],rehashidx设置为-1,将ht[0]设置为ht[1];
rehash期间,新增操作都在ht[1]上操作,其它操作先在ht[0]上,如果没有,就在ht[1]上操作;
跳跃表
跳跃表是在有序表的基础上,增加多层指向其他节点的指针加快访问,能达到O(logN)的复杂度。
结构
表节点中保存着后退指针、分值、成员对象,还包括多个层,每层中保存前进指针和跨度
跳跃表
压缩列表
压缩列表是为了节约内存,由一定规则编码的连续内存块组成的顺序结构。
各部分的含义:
- zlbytes 长度4字节,记录着整个压缩列表的字节数;
- zltail 长度4字节,记录尾节点距离压缩列表起始地址得字节数;
- zllen 长度2字节,记录压缩列表包含的节点数;
- entryX 节点,长度由内部结构决定;
- zlend 长度1字节,特殊值0xFF,标记压缩列表结束;
节点结构
- previous_entry_length 前一个节点的字节数,pre_ptr+previous_entry_length=curent_ptr; 如果前一个节点长度小于254,就用一个字节;如果前一个节点长度大于254,第一个字节为0xFE,用后面4个字节
- encoding 记录节点类型,01开头的,长度2字节,10开头的,长度5字节,其他都用1字节;
- content 节点内容,长度由类型决定;
在压缩列表中间位置插入节点和删除节点会导致连锁更新previous_entry_length
对象
redis 中键和值都使用对象,对象底层使用上面的数据结构
类型有字符串、列表、哈希、集合、有序集合
编码对应底层数据结构类型
事件
redis服务器是事件驱动的,分为文件事件和时间事件。
文件事件处理器
文件事件处理器包括套接字、IO多路复用程序、事件派发器和处理器。IO多路复用程序监听套接字,将产生事件的套接字放在队列里,每次发送一个套接字给派发器,上一个处理完再发下一个;事件派发器根据事件类型选择不同的处理器处理。
事件分为可读事件和可写事件。
事件处理器分为连接应答处理器、命令请求处理器、命令回复处理器。
时间事件处理器
时间事件分为定时事件和周期性事件。事件的主要属性有自增序列ID、事件到达时间when、时间事件处理器timeProc。
时间事件创建时头插法放到链表中,每当时间执行器执行时,遍历链表针对已经到达的事件,调用处理函数。
持久化
redis提供了持久化功能将内存数据保存到磁盘,避免数据丢失。
RDB持久化
RDB文件保存着键的二进制文件,通过save和bgsave命令生成RDB文件。
save会阻塞服务器,拒绝客户端的读写请求。
bgsave 在子进程中操作,服务器仍然可以处理客户端请求。
AOF持久化
服务器执行完写命令后,追加到AOF_BUF缓冲区中,每次事件循环结束时将缓存区内容刷新到操作系统的文件缓冲区,在写入磁盘。
单机数据库
数据库
整个服务器的状态保存在RedisServer结构中;
RedisServer 中包含db数组,每个元素对应一个数据库;
每个客户端都对应着一个RedisClient结构,其中引用着正在使用的数据库;
db结构中,包含字典类型的键空间dict,而expires字典中存着每个键对应的毫秒级过期时间;
键的过期策略:
- 定时删除,通过定时器,在键过期时立即删除
- 惰性删除,获取键时,如果过期时就删除
- 定期删除,每隔一段时间,扫描键,删掉过期的键
客户端
服务器中为每个客户端创建了RedisClient结构
- 输入缓冲区,客户端发送的原始命令,以一定的协议格式存储在
querybuf
输入缓冲区中; - 命令数组,用于存放命令解析结果;
- 命令解析后,会查找命令表,将命令实现函数放到
cmd
中; - 命令处理后的回复放在
buf
缓冲区中,比较长的放在reply
;
服务器
服务器启动
- 服务器创建必须的数据结构;
- 从配置文件读取配置;
- 初始化数据结构;
- 载入持久化文件还原数据库;
- 执行事件循环;
命令请求过程
- 客户端发送命令请求,用户键入一个命令后,客户端会以协议的格式通过套接字发送到服务器;
- 因为客户端命令的写入,使得套接字可读,服务器调用命令处理器,将协议格式的命令保存到客户端结构的输入缓冲区
querybuf
中,进而解析到参数数组中; - 调用命令执行器,在命令表中查找命令实现函数放到
cmd
中,经过预处理后,调用命令实现函数处理,将命令回复放到输出缓冲区里面,并关联命令回复处理器; - 当套接字变为可写状态,就执行命令回复处理器,将命令回复发送给客户端;
- 客户端接受到命令回复,进行协议格式的解析及后续处理;