zoukankan      html  css  js  c++  java
  • redis源码笔记 serverCron

    serverCron是redis每隔100ms执行的一个循环事件,由ae事件框架驱动。其主要执行如下任务:

    1.记录循环时间:

    server.unixtime = time(NULL)

    redis使用全局状态cache了当前的时间值。在vm实现以及lru实现中,均需要对每一个对象的访问记录其时间,在这种情况下,对精度的要求并不高(100ms内的访问值一样是没有问题的)。使用cache的时间值,其代价要远远低于每次均调用time()系统调用

    2.更新LRUClock值:

    updateLRUClock()

    后续在执行lru淘汰策略时,作为比较的基准值。redis默认的时间精度是10s(#define REDIS_LRU_CLOCK_RESOLUTION 10),保存lru clock的变量共有22bit。换算成总的时间为1.5 year(每隔1.5年循环一次)。

    不知为何在最初设计的时候,为lru clock只给了22bit的空间。

    3.更新峰值内存占用:

     550     if (zmalloc_used_memory() > server.stat_peak_memory)
     551         server.stat_peak_memory = zmalloc_used_memory();

    4.处理shutdown_asap

    在上一篇blog中,介绍了redis对SIG_TERM信号的处理。其信号处理函数中并没有立即终止进程的执行,而是选择了标记shutdown_asap flag,然后在serverCron中通过执行prepareForShutdown函数,优雅的退出。

     555     if (server.shutdown_asap) {
     556         if (prepareForShutdown() == REDIS_OK) exit(0);
     557         redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
     558     }

    在prepareForShutdown函数中,redis处理了rdb、aof记录文件退出的情况,最后保存了一次rdb文件,关闭了相关的文件描述符以及删除了保存pid的文件(server.pidfile).

    5.打印统计信息

    统计信息分为两类,两类统计信息均为每5s输出一次。第一类是key数目、设置了超时值的key数目、以及当前的hashtable的槽位数:

     561     for (j = 0; j < server.dbnum; j++) {
     562         long long size, used, vkeys;
     563 
     564         size = dictSlots(server.db[j].dict);
     565         used = dictSize(server.db[j].dict);
     566         vkeys = dictSize(server.db[j].expires);
     567         if (!(loops % 50) && (used || vkeys)) {
     568             redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
     569             /* dictPrintStats(server.dict); */
     570         }
     571     }

    第二类是当前的client数目,slaves数目,以及总体的内存使用情况:

     585     if (!(loops % 50)) {
     586         redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use",
     587             listLength(server.clients)-listLength(server.slaves),
     588             listLength(server.slaves),
     589             zmalloc_used_memory());
     590     }

    6.尝试resize hash表

    因为现在的操作系统fork进程均大多数采用的是copy-on-write,为了避免resize哈希表造成的无谓的页面拷贝,在有后台的rdb save进程或是rdb rewrite进程时,不会尝试resize哈希表。

    否则,将会每隔1s,进行一次resize哈希表的尝试;同时,如果设置了递增式rehash(redis默认是设置的),每次serverCron执行,均会尝试执行一次递增式rehash操作(占用1ms的CPU时间);

    579     if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1) {
    580         if (!(loops % 10)) tryResizeHashTables();
    581         if (server.activerehashing) incrementallyRehash();
    582     }

    7.关闭超时的客户端

    每隔10s进行一次尝试

     593     if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)
     594         closeTimedoutClients();

    8.如果用户在此期间,请求进行aof的rewrite操作,调度执行rewrite

     598     if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1 &&
     599         server.aofrewrite_scheduled)
     600     {       
     601         rewriteAppendOnlyFileBackground();
     602     }     

    9.如果有后台的save rdb操作或是rewrite操作:

    调用wait3获取子进程状态。此wait3为非阻塞(设置了WNOHANG flag)。注意:APUE2在进程控制章节其实挺不提倡用wait3和wait4接口的,不过redis的作者貌似对这个情有独钟。如果后台进程刚好退出,调用backgroundSaveDoneHandler或backgroundRewriteDoneHandler进行必要的善后工作,并更新dict resize policy(如果已经没有后台进程了,就可以允许执行resize操作了)。

     605     if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) {
     606         int statloc;
     607         pid_t pid;
     608     
     609         if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
     610             if (pid == server.bgsavechildpid) {
     611                 backgroundSaveDoneHandler(statloc);
     612             } else {
     613                 backgroundRewriteDoneHandler(statloc);
     614             }
     615             updateDictResizePolicy();
     616         }

    10.否则,如果没有后台的save rdb操作及rewrite操作:

    首先,根据saveparams规定的rdb save策略,如果满足条件,执行后台rdbSave操作;

    其次,根据aofrewrite策略,如果当前aof文件增长的规模,要求触发rewrite操作,则执行后台的rewrite操作。

     622          for (j = 0; j < server.saveparamslen; j++) {
     623             struct saveparam *sp = server.saveparams+j;
     624         
     625             if (server.dirty >= sp->changes &&
     626                 now-server.lastsave > sp->seconds) {
     627                 redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
     628                     sp->changes, sp->seconds);
     629                 rdbSaveBackground(server.dbfilename);
     630                 break;
     631             }
     632          }      
     633 
     634          /* Trigger an AOF rewrite if needed */
     635          if (server.bgsavechildpid == -1 &&
     636              server.bgrewritechildpid == -1 &&
     637              server.auto_aofrewrite_perc &&
     638              server.appendonly_current_size > server.auto_aofrewrite_min_size)
     639          {
     640             long long base = server.auto_aofrewrite_base_size ?
     641                             server.auto_aofrewrite_base_size : 1;
     642             long long growth = (server.appendonly_current_size*100/base) - 100;
     643             if (growth >= server.auto_aofrewrite_perc) {
     644                 redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
     645                 rewriteAppendOnlyFileBackground();
     646             }
     647         }

    11.如果推迟执行aof flush,则进行flush操作,调用flushAppendOnlyFile函数;

    12.如果此redis instance为master,则调用activeExpireCycle,对过期值进行处理(slave只等待master的DEL,保持slave和master的严格一致);

    13.最后,每隔1s,调用replicationCron,执行与replication相关的操作。

    在blog的最后,对serverCron的开头结尾进行简单的探讨;

    serverCron开头,有这样几行代码:

     525     REDIS_NOTUSED(eventLoop);
     526     REDIS_NOTUSED(id);
     527     REDIS_NOTUSED(clientData);

    表明,这个时间处理例程内部,对aeCreateTimeEvent规定的函数原型所传的参数,均没有使用。redis的ae库据作者所说,是参考libevent的实现精简再精简得到的,猜测其接口的设计也是借鉴了很多libevent的接口设计风格。

    serverCron最后,return 100。表明server将会在100ms后重新调用这个例程的执行。

  • 相关阅读:
    Android 主题theme说明 摘记
    Android开发 去掉标题栏方法 摘记
    安卓项目五子棋代码详解(二)
    关于 ake sure class name exists, is public, and has an empty constructor that is public
    百度地图3.0实现图文并茂的覆盖物
    android onSaveInstanceState()及其配对方法。
    关于集成科大讯飞语音识别的 一个问题总结
    android 关于 webview 控制其它view的显示 以及更改view数据失败的问题总结
    C# 解析 json Newtonsoft果然强大,代码写的真好
    c#数据类型 与sql的对应关系 以及 取值范围
  • 原文地址:https://www.cnblogs.com/liuhao/p/2538751.html
Copyright © 2011-2022 走看看