eventloop
redis 单线程的执行方式如下
while true{
time = 获取将要执行的定时时间时间
//该方法在有io事件的时候立即返回,如果一直没有io事件,则阻塞到time事件后自动唤醒
io事件 = 阻塞获取io时间(time)
if (io事件不为空){
处理io事件
}
if(有到时的定时事件){
处理定时事件
}
处理aof写入
向从客户端发送同步命令
}
select模型
传统的io编程模型,是为每一个client socket分配一个线程或者进程,负载有限
select 模型则把connnection 和 内容处理分开。使用一个线程监听所有的client socket,也就是select阻塞调用,当有socket有读写事件的时候,select调用会返回。此时,用户可以遍历所有的socket fd ,判断其中哪些由读写发生,交由其他的线程处理。
select的缺点:
- 同时监听的
fd数量有限。且很难修改该参数。 - 每次调用返回所有的监听句柄,由使用者进行遍历,寻找实际发生的句柄进行处理
- 句柄的复制成本较大
epoll 是 select的改进型。每次返回的都是实际发生读写事件的句柄,且监听数量远远大于select。缺点主要是每次读写事件唤醒的时候,socket 的输入缓冲区可能没有获取到所有的输入内容,需要等到最后一次内容完整的时候才能进行处理
redis 在使用select获取到所有发生读写的事件后,会将其进行放入一个队列。 然后eventloop会从中读取执行。
定时事件
redis的定时事件由一个结构体定义。
struct {
int id,
int timestamp,//执行事件
void* handler //执行函数指针
}
所有的定时事件以链表的形式保存。每次需要处理定时任务的时候,遍历该链表,从中找到需要执行的事件进行处理
serverCron
redis每100ms执行一次serverCron方法。 也是redis主要的定时方法。类似持久化,aof 重写,过期键处理都是在该方法中执行。
- 修改服务器缓存的时间,用于一些不太精准的地方
- 修改服务器缓存的lru时间,用于清除空转的对象
- 更新每秒执行次数,采样计算
- 更新内存峰值
- 拦截关闭信号,检查服务器
shutdown_asap,判断是否关闭服务,并在关闭前执行持久化。shutdown_asap由redis 拦截进程的关闭信号处理设置的 - 对一定数量的客户端进行清除空转和重置缓冲区
- 删除过期键等管理
- 执行延迟的
bgrewriteaof.bgsave的时候会延迟bgrewriteaof命令 - 检查
rdb_child_pid和aof_child_pid,如果不等于-1,执行wait3函数,检查子进程是否执行结束,如果结束,执行后续操作。如果没有的化,是否自动触发rdb操作,aof重写 - 写入
aof缓冲区内容到aof文件
没想到关闭,rdb,aof 的处理都是在定时任务中处理的
命令执行过程
-
将命令写入对应
client的输入缓冲区 -
解析命令,设置参数,和个数
-
查找
命令对应的redicommand -
判断能否执行命令
- 是否登录
- 命令是否正确输入
- 设置了最大内存的情况下,是否有充足的内存
- 是否在执行同步rdb
- 从服务器数量是否足够
- 是否是订阅客户端
- 是否是在执行事务
-
执行命令
-
执行aof 写入
-
发送命令到从服务器(这一步没有ack,通过心跳来同步主从之间的进度,如何高效的发送命令给多个从服务器)
执行细节
- 不会抢占执行
- 耗时的任务,例如持久化会交给子进程处理