Redis原理分析
写在前面:
因为本人主语言是Java、辅助语言是python,对C语言研究很浅
所以我的分析可能更多的是现象级别的,并且用类python伪代码说明执行流程
事件机制
Redis是一个事件驱动程序,服务器中有两种事件需要处理
1、文件事件(file event) :主要是网络读写事件
2、时间事件(time event) :处理一些周期性或者定时的操作,比如后台检测当前是否符合rdb生成条件
文件事件
Reids基于Reactor网络模型实现了一套网络事件处理器(file event handler)
如图所示
IO多路复用程序监听多个套接字上的事件发生情况,并且根据事件的类型文件事件分派器会分派不同的事件处理器去处理对应的事件
这个文件事件处理器虽然是以单线程的方式运行的,但是IO多路复用程序同时监听多个套接字,性能也是非常不错
工作流程
IO多路复用程序监听多个套接字
在套接字发生了事件的时候,将其封装并加入到发生事件的套接字队列中
文件事件分派器会不断从这个队列中取出事件,根据事件类型分派对应的事件处理器
如果套接字久久没有发送事件,IO多路复用程序会一直阻塞,直到超时后,处理时间事件,接着进入下一轮阻塞
时间事件
Redis中有两类时间事件
1、定时事件 :让一段程序在指定的时间之后执行一次
2、周期事件 :让一段程序每隔一段时间执行一次
周期事件其实就是定时事件的集合
举个例子,现在有一个30s后执行的定时事件
在30s后触发了这个定时事件,只要继续在30s后执行这个定时事件,就成为了周期事件
一个时间事件由以下3个部分组成
- id:时间事件的全局唯一id
- when:记录下一次发生时间事件的事件,UNIX时间戳
- timeProc:时间事件处理器,具体要做什么事情
决定一个定时事件是否需要继续执行的是时间事件处理器的返回值:
AE_NORMAL
:这个事件是定时事件,执行完毕后就可以被删除了- 非
AE_NORMAL
:这个事件是周期事件,执行完毕后仍然需要在这个返回值的时间之后继续执行这个周期事件的逻辑
实现
服务器将所有的时间事件都存储在了一个无序链表(不根据when排序,根据id降序排序)中
每当时间事件处理器调用processTimeEvents的时候,就会遍历每一个时间事件,判断当前时间事件是否已经到达
判断逻辑就是取得当前时间的时间戳,如果已经大于等于when中的时间戳了,说明已经触发时间事件了,调用timeProc函数
PS:Redis一般情况下只有一个叫serverCorn的周期事件
也就是说这个time_events链表中可能只有一个节点,不会因为遍历操作的O(N)复杂度降低效率
下面写一下processTimeEvents的伪代码
def processTimeEvents:
# 拿到第一个时间事件
time_event = time_events
while time_event is not None:
# 时间事件没有到达
if now_unix() < time_event.when:
continue
# 下一次时间事件需要等待的时间
next_arrive_time = time_event.timeProc()
if next_arrive_time == AR_NOEMAL:
# 这是一个定时时间事件,只执行一次
delete_time_event(time_event)
else:
# 更新下一次时间事件的触发时间
time_event.when = time_event.when + next_arrive_time
serverCorn的工作
1、更新服务器的各类统计信息,例如运行时间、内存占用情况
2、根据过期策略清理数据库的过期key
3、关闭和清理无效的客户端连接
4、检查是否需要开启rdb的bgsave、根据aof策略判断是否需要将aof_buf刷盘(flushAppendOnlyFile)
5、如果当前服务器是主服务器或者集群模式,则对从服务器做同步操作
serverCorn默认每s运行10次,也就是100毫秒运行一次
可以在redis.conf中通过hz
配置serverCorn的每s执行次数
事件的调度和执行
因为同时存在两种事件,所以服务器必须对这两种事件进行调度,调度逻辑如下伪代码所示
def processEvents:
# 得到距离当前时间最近的时间事件
next_time_event = nearest_time_event()
# 还需要等待的时间
need_wait_time = now_unix() - next_time_event.when
# 如果小于0,说明时间事件已经超时了,原因是上一次事件循环中,处理文件事件时间过长
if need_wait_time < 0:
need_wait_time = 0
# 多路复用程序,在多个套接字上等待,最多等待need_wait_time时间
select(need_wait_time)
# 处理文件事件
processFileEvents()
# 处理时间事件
processTimeEvents()
可以看到首先是检查最近的时间事件还有多久时间发生
多路复用程序根据这个时间设置最大超时时间
最大程度的保证了下一次的时间事件不会超时太久
但是也有可能会因为文件事件处理时间过长导致时间事件超时
将这个函数放到一个循环中,也就构成了redis服务器的主函数
def main:
# 初始化服务器,涉及数据恢复、初始化服务器结构等等操作
init_server()
# 只要服务器没有关闭,就一直处理事件
while !redis_server.shudown:
processEvents()
# 善后工作
destory_server()