redis--时间事件

Redis的事件分为文件事件(file event)时间事件(time event), 现在已知的时间事件就是定时任务serverCron()函数, 其每隔100ms执行一次; 该函数实在main方法中初始化时注册到时间事件中

// 使用一个宏定义:run_with_period(milliseconds) { .... },实现一部分代码有次数限制的被执行
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    int j;
    UNUSED(eventLoop);
    UNUSED(id);
    UNUSED(clientData);

    // 如果设置了看门狗,则在过期时间内,递达一个 SIGALRM 信号
    if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);

    // 设置服务器的时间缓存
    updateCachedTime();

    // 更新服务器的一些统计值
    run_with_period(100) {
        // 命令执行的次数
        trackInstantaneousMetric(STATS_METRIC_COMMAND,server.stat_numcommands);
        // 从网络读到的字节数
        trackInstantaneousMetric(STATS_METRIC_NET_INPUT,
                server.stat_net_input_bytes);
        // 已经写到网络的字节数
        trackInstantaneousMetric(STATS_METRIC_NET_OUTPUT,
                server.stat_net_output_bytes);
    }

    // 服务器的LRU时间表示位数为24位,因此最长表示2^24秒,大约1.5年,只要在1.5年内,该对象被访问,那么就不会出现对象的LRU时间比服务器的时钟还要年轻的现象
    // LRU_CLOCK_RESOLUTION 可以改变LRU时间的精度

    // 获取服务器的LRU时钟
    server.lruclock = getLRUClock();

    // 更新服务器的最大内存使用量峰值
    if (zmalloc_used_memory() > server.stat_peak_memory)
        server.stat_peak_memory = zmalloc_used_memory();

    // 更新常驻内存的大小
    server.resident_set_size = zmalloc_get_rss();

    // 安全的关闭服务器
    if (server.shutdown_asap) {
        // 关闭服务器前的准备动作,成功则关闭服务器
        if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) exit(0);
        // 失败则打印日志
        serverLog(LL_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
        // 撤销关闭服务器标志
        server.shutdown_asap = 0;
    }

    // 打印数据库的信息到日志中
    run_with_period(5000) {
        // 遍历数据库
        for (j = 0; j < server.dbnum; j++) {
            long long size, used, vkeys;

            // 获取当前数据库的键值对字典的槽位数,键值对字典已使用的数量,过期键字典已使用的数量
            size = dictSlots(server.db[j].dict);
            used = dictSize(server.db[j].dict);
            vkeys = dictSize(server.db[j].expires);
            // 打印到日志中
            if (used || vkeys) {
                serverLog(LL_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
                /* dictPrintStats(server.dict); */
            }
        }
    }

    // 如果服务器不在哨兵模式下,那么周期性打印一些连接client的信息到日志中
    if (!server.sentinel_mode) {
        run_with_period(5000) {
            serverLog(LL_VERBOSE,
                "%lu clients connected (%lu slaves), %zu bytes in use",
                listLength(server.clients)-listLength(server.slaves),
                listLength(server.slaves),
                zmalloc_used_memory());
        }
    }

    // 执行client的周期性任务
    clientsCron();

    // 执行数据库的周期性任务
    databasesCron();

    // 如果当前没有正在进行RDB和AOF持久化操作,且AOF重写操作被提上了日程,那么在后台执行AOF的重写操作
    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
        server.aof_rewrite_scheduled)
    {
        rewriteAppendOnlyFileBackground();
    }

    // 如果正在进行RDB或AOF重写等操作,那么等待接收子进程发来的信息
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        int statloc;
        pid_t pid;

        // 接收所有子进程发送的信号,非阻塞
        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            // 获取退出码
            int exitcode = WEXITSTATUS(statloc);
            int bysignal = 0;

            // 判断子进程是否因为信号而终止,是的话,取得子进程因信号而中止的信号码
            if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);

            // 子进程没有退出,还在进行RDB或AOF重写等操作
            if (pid == -1) {
                // 打印日志
                serverLog(LL_WARNING,"wait3() returned an error: %s. "
                    "rdb_child_pid = %d, aof_child_pid = %d",
                    strerror(errno),
                    (int) server.rdb_child_pid,
                    (int) server.aof_child_pid);
            // RDB持久化完成
            } else if (pid == server.rdb_child_pid) {
                // 将RDB文件写入磁盘或网络中
                backgroundSaveDoneHandler(exitcode,bysignal);
            // AOF持久化完成
            } else if (pid == server.aof_child_pid) {
                // 将重写缓冲区的命令追加AOF文件中,且进行同步操作
                backgroundRewriteDoneHandler(exitcode,bysignal);
            // 其他子进程,打印日志
            } else {
                if (!ldbRemoveChild(pid)) {
                    serverLog(LL_WARNING,
                        "Warning, detected child with unmatched pid: %ld",
                        (long)pid);
                }
            }
            // 更新能否resize哈希的策略
            updateDictResizePolicy();
        }

    // 没有正在进行RDB或AOF重写等操作,那么检查是否需要执行
    } else {
        // 遍历save命令的参数数组
         for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

            // 数据库的键被修改的次数大于SAVE命令参数指定的修改次数,且已经过了SAVE命令参数指定的秒数
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 CONFIG_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                // 进行 BGSAVE 操作
                rdbSaveBackground(server.rdb_filename);
                break;
            }
         }

         // 是否触发AOF重写操作
         if (server.rdb_child_pid == -1 &&
             server.aof_child_pid == -1 &&
             server.aof_rewrite_perc &&
             server.aof_current_size > server.aof_rewrite_min_size)
         {
            // 上一次重写后的大小
            long long base = server.aof_rewrite_base_size ?
                            server.aof_rewrite_base_size : 1;
            // AOF文件增长的百分比
            long long growth = (server.aof_current_size*100/base) - 100;
            // 大于设置的百分比100则进行AOF后台重写
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
         }
    }

    // 将AOF缓存冲洗到磁盘中
    if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);

    // 当AOF重写操作,同样将重写缓冲区的数据刷新到AOF文件中
    run_with_period(1000) {
        if (server.aof_last_write_status == C_ERR)
            flushAppendOnlyFile(0);
    }

    // 释放被设置为异步释放的client
    freeClientsInAsyncFreeQueue();

    // 解除client的暂停状态
    clientsArePaused(); /* Don't check return value, just use the side effect. */

    // 周期性执行复制的任务
    run_with_period(1000) replicationCron();

    /* Run the Redis Cluster cron. */
    // 周期性执行集群任务
    run_with_period(100) {
        if (server.cluster_enabled) clusterCron();
    }

    //周期性执行哨兵任务
    run_with_period(100) {
        if (server.sentinel_mode) sentinelTimer();
    }

    // 清理过期的被缓存的sockets连接
    run_with_period(1000) {
        migrateCloseTimedoutSockets();
    }

    // 如果 BGSAVE 被提上过日程,那么进行BGSAVE操作,因为AOF重写操作在更新
    // 注意:此代码必须在上面的replicationCron()调用之后,确保在重构此文件以保持此顺序时。 这是有用的,因为我们希望优先考虑RDB节省的复制
    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
        server.rdb_bgsave_scheduled &&
        (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
         server.lastbgsave_status == C_OK))
    {
        // 更新执行BGSAVE,成功则清除rdb_bgsave_scheduled标志
        if (rdbSaveBackground(server.rdb_filename) == C_OK)
            server.rdb_bgsave_scheduled = 0;
    }

    // 周期loop计数器加1
    server.cronloops++;
    // 返回周期,默认为100ms
    return 1000/server.hz;
}

大致总结列出部分:

  • 主动删除过期的键(也可以在读数据库时被动删除)
  • 喂看门狗 watchdog
  • 更新一些统计值
  • 渐进式rehash
  • 触发AOF 的重写操作(重写开启新进程)
  • 如果AOF正在重写,接受重写结束后的信号,将子进程里的临时aof文件写入磁盘,并将部分重写缓冲追加进aof文件
  • 触发AOF缓冲写入 内核/磁盘(刷新开启新线程)
  • 不同状态的client的超时
  • 复制重连
  • ……
posted @ 2019-07-11 13:48  車輪の唄  阅读(28)  评论(0)    收藏  举报  来源