曹工说Redis源码(7)-- redis server 的周期执行任务,到底要作些啥

文章导航

Redis源码系列的初衷,是帮助咱们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续能够本身阅读源码,或者跟着我这边一块儿阅读。因为我用c也是好几年之前了,些许错误在所不免,但愿读者能不吝指出。html

曹工说Redis源码(1)-- redis debug环境搭建,使用clion,达到和调试java同样的效果java

曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充redis

曹工说Redis源码(3)-- redis server 启动过程完整解析(中)算法

曹工说Redis源码(4)-- 经过redis server源码来理解 listen 函数中的 backlog 参数数据库

曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工做解析(下)缓存

曹工说Redis源码(6)-- redis server 主循环大致流程解析安全

本讲主题

本讲,聚焦于redis的周期执行任务。redis启动起来后,基本就剩下两件事,上一讲的主流程分析中,已经讲到了。1个是处理客户端请求,2就是指向周期任务。处理客户端请求,大概会细分为:处理客户端链接事件(客户端链接到redis)、客户端读写事件(客户端发送请求,redis返回响应);服务器

周期任务呢,就是本讲主题,let's go。app

周期任务的大致流程

周期任务,上一讲已经提到,其就是一个函数指针,具体实现,就是redis.c中的 serverCron 函数。函数

该函数的大的流程,按照代码中的执行顺序,咱们先了解下:

  1. 注册一个watchdog,注册方式是经过一个timer,注册了该timer以后,会按期给当前进程,触发一个SIGALRM信号,触发了这个信号后,会干吗呢,会回调位于 debug.c 文件中的 watchdogSignalHandler方法,这个方法,主要是在redis执行一些命令时,超过指定时长后,打印一些debug日志。

    能够参考:

    Redis 2.6 的新特性:Watchdog(看门狗)

    Redis software watchdog

  2. 更新server时间,redis server在不少时候,都须要获取当前时间,就像咱们写业务代码差很少,可是,redis比较扣,扣什么?扣性能。在不须要获取当前时间的时候,redis以为,获取一个不那么准确的时间就好了。因此,就缓存了一个全局时间,这个全局时间,何时刷新呢,就在这个周期任务中。

    你们仔细看注释吧:

    /* We take a cached value of the unix time in the global state because with
     * virtual memory and aging there is to store the current time in objects at
     * every object access, and accuracy is not needed. To access a global var is
     * a lot faster than calling time(NULL) */
    void updateCachedTime(void) {
        server.unixtime = time(NULL);
        server.mstime = mstime();
    }

    简单翻译下,就是说,每一个对象,每次被访问的时候,有个access-time,这个时间,不须要那么精确,不必每次去new date(),使用缓存的时间就好了,这样能比较快。全局时间,缓存在server.unixtime 和 server.mstime中。

  3. 计算redis的ops,相似于tps;这个操做,不是每次该周期任务时,都要执行,而是自定义执行的周期,整体来讲,没有本周期任务那么频繁。

    redis中,定义了一个宏来实现这个功能,好比:

    // 记录服务器执行命令的次数
        run_with_period(100) trackOperationsPerSecond();

    这个就是,每100ms执行一次上面的这个操做。

    这个怎么去计算ops(operation per second)呢?看下面的代码即懂:

    void trackOperationsPerSecond(void) {
    
        // 计算两次抽样之间的时间长度,毫秒格式
        long long t = mstime() - server.ops_sec_last_sample_time;
    
        // 计算两次抽样之间,执行了多少个命令
        long long ops = server.stat_numcommands - server.ops_sec_last_sample_ops;
    
        long long ops_sec;
    
        //1 计算距离上一次抽样以后,每秒执行命令的数量
        ops_sec = t > 0 ? (ops * 1000 / t) : 0;
    	...
    }

    1处,分子分母,你们一看,应该就懂了。ops = 一段时间内的操做数量/ 时间长度。

  4. 刷新服务器的 LRU 时间,目前,我以为能够简单理解为:redis的空间大小是有限的,假设机器内存10g,那么不可能把数据库的几个t的数据都放redis,因此基本是放热数据,那不热的数据怎么办?被清除。清除的算法,就是lru。每一个key,无论设没设过时时间,都会维护一个lruClock,即最近一次被访问的时间。

    计算一个对象的空闲时长,就是用服务器的LRU时间 减去 key的LRU时间。

    // 使用近似 LRU 算法,计算出给定对象的闲置时长
    unsigned long long estimateObjectIdleTime(robj *o) {
        unsigned long long lruclock = LRU_CLOCK();
        if (lruclock >= o->lru) {
            return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
        } else {
            return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) *
                        REDIS_LRU_CLOCK_RESOLUTION;
        }
    }

    网上的一篇文章写得不错,能够参考:

    redis的LRU策略理解

  5. 记录服务器的内存峰值

    /* Record the max memory used since the server was started. */
        // 记录服务器的内存峰值
        if (zmalloc_used_memory() > server.stat_peak_memory)
            server.stat_peak_memory = zmalloc_used_memory();

    何时用呢?好像只在info命令里看到使用了。

  6. 判断服务器的关闭标识是否打开,如打开,则关闭

    // 服务器进程收到 SIGTERM 信号,关闭服务器
        if (server.shutdown_asap) {
            // 尝试关闭服务器
            if (prepareForShutdown(0) == REDIS_OK) exit(0);
         }
  7. 打印数据库的键值对信息、客户端信息

    单纯的log操做,惟一注意的是,要把日志级别调到REDIS_VERBOSE才看获得

  8. 检查客户端空闲时长,关闭空闲超时的客户端

    int clientsCronHandleTimeout(redisClient *c) {
    
        // 获取当前时间
        time_t now = server.unixtime;
    
        // 服务器设置了 maxidletime 时间
        if (server.maxidletime &&
            ...
            // 客户端最后一次与服务器通信的时间已经超过了 maxidletime 时间
            (now - c->lastinteraction > server.maxidletime)) {
            redisLog(REDIS_VERBOSE, "Closing idle client");
            // 关闭超时客户端
            freeClient(c);
            return 1;
        }
        ...
    }
  9. 对数据库执行各类操做

    /* This function handles 'background' operations we are required to do
     * incrementally in Redis databases, such as active key expiring, resizing,
     * rehashing. */
    // 对数据库执行删除过时键,调整大小,以及主动和渐进式 rehash
    void databasesCron(void)

    看注释可知,大概有以下工做:删除过时key,hash表的rehash,hash的size调整(若是字典的使用率低,会缩小其占用的内存大小)

    后续会详解这部分。

  10. 若是当前没有aof或者rdb后台任务正在执行,且server以前被schedule了一个aof rewrite后台任务,则执行

    aof 重写。(aof记录了每一条命令,时间长了,会重复,好比先把key a设为1,再设为2,再设为3,这样,aof中有3条记录,实际上,只须要一条便可,因此会重写)

    aof 重写在一个子进程中进行,子进程完成后,会给当前进程发送信号,因此,当前进程会一直等待信号,等待子进程完成后,本身再作些处理。

    好比,主进程要作什么处理呢?在 aof 重写期间,主进程可能仍是要不断地处理命令(这里不会无限期等待,此次等不到就到下一次周期任务时再等),这期间,处理的命令,不能记录到aof文件中,省得影响正在进行aof 重写的子进程,因此,主进程会把这期间的命令,记录到一个小本本上。

    等到子进程写完了,主进程再把小本本上的aof命令,写到aof日志文件里。

  11. 若是当前没有aof或者rdb后台任务在执行,也没有被schedule 一个aof rewrite任务,那么,上面这步中的所有操做,都不会发生。

    此时,会去检查,当前是否知足aof 重写、rdb 保存的条件。

    好比,rdb不是通常须要配置以下参数吗:

    save 900 1
    save 300 10
    save 60 10000

    此时,就会去检查,这些参数,是否知足,若是知足,就要开始进行rdb后台保存。

    或者,当如下的aof参数知足时,也会触发aof重写:

    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
  12. 根据配置的aof fsync策略,决定是否要刷新到文件中

    前面咱们说的aof写日志文件,不必定真的就写入了文件,可能还在OS cache中,须要调用 fsync 才能写入到文件中。

    这里即对应配置文件中的:

    # appendfsync always
    appendfsync everysec
    # appendfsync no

    默认每秒执行一次fsync,性能和数据安全性的折衷。

  13. 涉及slave、cluster、sentinel的部分操做

    若是运行在以上几种模式下,会涉及到对应的一些周期操做,后续再涉及这块。

总结

本讲的主题大概是这些,其中,细节部分,好比数据库的周期任务等,留待下讲继续。

相关文章
相关标签/搜索