深入理解Redis(二)—单机数据库
Redis单机数据库是一种典型的key-value数据库,数据存储在内存中大大提升了数据库操作速度,同时RDB和AOF持久化策略解决了内存型数据库的宕机后数据恢复问题.
个人主页:tuzhenyu’s page
原文地址:深入理解Redis(二)—单机数据库
(1)数据库服务端实现整体流程
初始化服务器
Redis的服务器进程是一个事件循环,事件主要包括文件事件和时间事件
处理文件事件:接收客户端指令,处理指令,发送回复;将新内容追加到aof_buf缓存中
处理时间事件
判断是否需要将aof_buf中的缓存内容写入和同步到aof文件中
//伪代码
initServer()
while true:
processFileEvent()
processTimeEvent()
flushAppendOnly()
(2)初始化服务器
数据库服务端的初始化主要包括四步:
初始化服务器状态结构,设置默认参数initServerConfig
设置服务器默认配置文件路径
设置服务器默认端口号
创建命令列表
载入配置选项,修改系统参数,根据启动命令载入配置选项
redis-server redis.conf //修改配置文件路径
redis-server --port 10086 //修改服务器端口号
- 初始化服务器数据结构,分配内存初始化值initServer
//伪代码
strcut redisServer{
list *clients; //客户端链表
list *db; //数据库链表
sds aof_buf; //AOF缓冲区,用于AOF持久化的写指令记录
}
还原数据库状态
完成了服务器server变量初始化后,服务器需要重新载入RDB文件或者AOF文件,并根据文件记录的内容还原数据库状态
如果启用了AOF持久化功能,那么使用AOF文件还原数据库状态
如果没有开启AOF持久化功能,那么服务器使用RDB文件来还原数据库状态
(3)处理文件事件
服务端和客户端通过socket通信产生相应的文件事件,服务端通过监听文件事件完成一系列的网络通信操作
文件事件处理器使用I/O复用单线程监听多个套接字,一旦其中的任意套接字产生事件都会调用文件事件处理器将相应的请求映射到对应的事件处理器上;
事件类型
AE_READABLE事件:客户端执行write操作或者connect操作时I/O多路复用程序监控到AE_READABLE事件
AE_WRITABLE事件:客户端准备好接收服务端数据时产生AE_WRITABLE事件
文件事件中的数据库操作
服务器redisServer数据结构中每个db指针对应一个redisDb数据结构用来保存一个数据库中的数据
键空间中的键是一个SDS字符串对象,每个值可以是字符串对象,列表对象,哈希对象,集合对象,有序集合对象中的任一种;
typedef struct redisDb{
dict *dict; //数据库键空间,保存着数据库中的所有键值对;
dict *expire; //过期键空间,保存所有设置了过期时间的键
}
数据库的基本操作就是在键空间上进行添加新键,删除键,更新键,根据键取值等操作
键的过期,通过expire关键字可以设置键的过期事件,在过期键空间*expire中创建键值对,对应的值不是对象而是long型的过期时间.
typedef struct redisDb{
dict *expire; //过期键空间,保存所有设置了过期时间的键
}
Redis过期键的删除策略
惰性删除策略:在执行实际的读写操作之前都会调用exprieIfNeeded()方法对输入键进行检查,如果过期就会删除键
定期删除策略:在serverCorn()函数中,在规定事件内(比如1ms)分多次遍历服务器中的各个数据库,从expires字典中随机检查一部分键的过期时间,并删除其中的过期键;(每次从一定数量的数据库中随机抽取一定数量的随机键进行检查)
定时删除是在设定过期时间的同时创建定时器,保证过期键的尽快删除,但是键的删除消耗CPU资源会对吞吐量产生很大影响;
(4)处理时间事件
时间操作是一系列定时操作的抽象
服务器将所有的时间事件放在一个无序链表中,每当时间事件处理程序processTimeEvents执行会遍历整个链表,查找已经到达时间的事件,并调用相应的事件处理器.
正常模式下Redis服务器只使用serverCron一个事件事件,因此无序链表不影响事件执行的性能
服务器唯一时间事件serverCron()平均100毫秒运行一次,也就是说serverCron()函数内的事件的基本单位是100毫秒
清理数据库中过期的键值对(expire)
尝试AOF或者RDB持久化操作
如果是主服务器,对从服务器进行定期同步操作
如果处于集群模式,对集群进行定期同步和连接测试
(5)事件调度
- 因为服务器中同时存在文件事件和时间事件,所以真实环境下文件事件和时间事件不是独立运行的而是在aeProcessEvents()函数中统一调度,服务器对两种事件进行调度.
//伪代码
def main:
init_server(); //初始化
while server_is_not_shutdown():
aeProcessEvents() //处理事件,包括文件事件和时间事件,以及具体的调度
cleanServer() //清理操作
- 调度机制,时间事件不会抢占文件事件的执行,因此时间事件的实际处理时间通常会比时间事件设定的到达时间稍晚一些
def aeProcessEvents():
time_evnet = aeSearchNearestTimer() //获取到达时间距离当前最近的时间事件
remaind_ms = time_evnet = unix_ts_now(); //计算最近的时间事件距当前还有多少毫秒
if remaind_ms<0
remaind_ms = 0;
aeApiPoll(remaind_ms) //调用poll多路复用,设定最大阻塞事件
processFileEvents() //如果在最大阻塞时间内有文件事件产生则执行文件事件
processTimeEents() //处理已经到达的时间事件;
(6)RDB持久化
Redis可以通过SAVE命令和BGSAVE命令进行RDB持久化
SAVE命令需要手动执行并且会阻塞服务器进程
BGSAVE命令是通过派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求
在Redis数据库在启动时,会检测服务器是否开启了AOF持久化功能,如果开启了则会优先使用AOF文件来还原数据库状态,如果没有开启,并且检测到RDB文件存在则会自动载入RDB文件进行数据库恢复;
Redis允许用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令,在serverCorn函数时间事件中检查save选项中设置的保存条件是否满足,如果满足就会执行BGSAVE指令;
(6)AOF持久化
RDB持久化通过保存数据库中的键值对来记录数据库状态,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态;
在文件事件处理器中,如果处理的是写指令会在写指令执行完毕后以协议的格式将被执行的写指令追加到服务器aof_buf缓冲区中;
服务器在每次结束一个事件循环(文件事件和时间事件)后都会调用flushAppendOnlyFile()方法,判断是否需要讲aof_buf缓冲区中的内容写入和同步到AOF文件中;其中flushAppendOnlyFile()方法有三个参数:
always:每次事件虚幻结束后都将aof_buf缓冲区内容写入并同步刷盘到AOF文件中;
everysec:每次事件循环结束都将aof_buf缓冲区中的内容写入AOF文件,如果上次同步刷盘AOF文件的时间距离现在超过一秒钟,那么就进行同步刷盘,并且刷盘操作是一个线程专门负责;
no:每次事件循环结束都将aof_buf缓冲区中的内容写入AOF文件,但是不进行刷盘操作,何时进行刷盘由脏页数目触发的操作系统刷盘决定;
AOF文件会随着指令的增加越来越大,因此需要重写操作,重写操作是根据数据库中现有数据合成新的SET指令;
为了防止AOF重写阻塞线程,使用BGREWRITEAOF指令将AOF重写程序放入子进程中,这样主进程依旧可以处理请求,同时利用AOF重写缓冲区,讲重写期间主进程处理的写请求记录到重写缓冲区中,在重写结束后写入新的AOF文件中,最后用新的AOF文件替换旧的AOF文件;
总结
Redis内存数据苦以Key-Value的形式实现了数据库基本的增删改查以及键过期等基本操作,AOF和RDB持久化避免了内存数据在数据宕机后无法恢复的问题,同时丰富的数据结构为数据库操作提供了便利