3.2 Redis 执行命令的过程

106 阅读3分钟

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的缺点:

  1. 同时监听的fd数量有限。且很难修改该参数。
  2. 每次调用返回所有的监听句柄,由使用者进行遍历,寻找实际发生的句柄进行处理
  3. 句柄的复制成本较大

epollselect的改进型。每次返回的都是实际发生读写事件的句柄,且监听数量远远大于select。缺点主要是每次读写事件唤醒的时候,socket 的输入缓冲区可能没有获取到所有的输入内容,需要等到最后一次内容完整的时候才能进行处理

redis 在使用select获取到所有发生读写的事件后,会将其进行放入一个队列。 然后eventloop会从中读取执行。

定时事件

redis的定时事件由一个结构体定义。

struct {
    int id,
    int timestamp,//执行事件
    void* handler //执行函数指针
}

所有的定时事件以链表的形式保存。每次需要处理定时任务的时候,遍历该链表,从中找到需要执行的事件进行处理

serverCron

redis100ms执行一次serverCron方法。 也是redis主要的定时方法。类似持久化,aof 重写,过期键处理都是在该方法中执行。

  1. 修改服务器缓存的时间,用于一些不太精准的地方
  2. 修改服务器缓存的lru时间,用于清除空转的对象
  3. 更新每秒执行次数,采样计算
  4. 更新内存峰值
  5. 拦截关闭信号,检查服务器shutdown_asap,判断是否关闭服务,并在关闭前执行持久化。shutdown_asapredis 拦截进程的关闭信号处理设置的
  6. 对一定数量的客户端进行清除空转和重置缓冲区
  7. 删除过期键等管理
  8. 执行延迟的bgrewriteaof.bgsave的时候会延迟bgrewriteaof命令
  9. 检查rdb_child_pid和aof_child_pid,如果不等于-1,执行wait3函数,检查子进程是否执行结束,如果结束,执行后续操作。如果没有的化,是否自动触发rdb操作,aof重写
  10. 写入aof缓冲区内容到aof文件

没想到关闭,rdb,aof 的处理都是在定时任务中处理的

命令执行过程

  1. 将命令写入对应client的输入缓冲区

  2. 解析命令,设置参数,和个数

  3. 查找命令对应的redicommand

  4. 判断能否执行命令

    1. 是否登录
    2. 命令是否正确输入
    3. 设置了最大内存的情况下,是否有充足的内存
    4. 是否在执行同步rdb
    5. 从服务器数量是否足够
    6. 是否是订阅客户端
    7. 是否是在执行事务
  5. 执行命令

  6. 执行aof 写入

  7. 发送命令到从服务器(这一步没有ack,通过心跳来同步主从之间的进度,如何高效的发送命令给多个从服务器)

执行细节

  1. 不会抢占执行
  2. 耗时的任务,例如持久化会交给子进程处理