• Redis源码分析:serverCron


     
    Redis源代码重要目录
    dict.c:也是很重要的两个文件,主要对于内存中的hash进行管理:
    adlist.c:用于对list的定义,它是个双向链表结构
    sds.c:用于对字符串的定义,从头文件可以找到:
    object.c:用于创建和释放redisObject对象
    sort.c:关于排序算法,sort.c具体作为Redis场景下的排序实现。

    multi.c:用于事务处理操作。
    rdb.c:对于Redis本地数据库的相关操作,默认文件是dump.rdb(通过配置文件获得),包括的操作包括保存,移除,查询等等。
    redis.c:服务端程序的实现。具体会在后面的文章详细介绍。
    ae.c:用于Redis的事件处理,包括句柄事件和超时事件。
    anet.c:这两个文件非常重要,作为Server/Client通信的基础封装,包括anetTcpServer,anetTcpConnect,anetTcpAccept,anetRead,anetWrite等等方法。
    aof.c:aof,全称为append only file,作用就是记录每次的写操作,在遇到断电等问题时可以用它来恢复数据库状态。但是他不是bin的,而是text的。一行一行,写得很规范.如果你是一台redis,那你也能人肉通过它恢复数据。
    db.c:对于Redis内存数据库的相关操作。
    zmalloc.c:关于Redis的内存分配的封装实现。
     
     
     

    http://www.cnblogs.com/liuhao/archive/2012/06/06/2538751.html

    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.更新峰值内存占用:

    550if (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函数,优雅的退出。

    555if (server.shutdown_asap) {
     556if (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的槽位数:

    复制代码
    561for (j = 0; j < server.dbnum; j++) {
     562longlong size, used, vkeys;
     563564         size = dictSlots(server.db[j].dict);
     565         used = dictSize(server.db[j].dict);
     566         vkeys = dictSize(server.db[j].expires);
     567if (!(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数目,以及总体的内存使用情况:

    复制代码
    585if (!(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时间);

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

    7.关闭超时的客户端

    每隔10s进行一次尝试

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

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

    598if (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操作了)。

    复制代码
    605if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) {
     606int statloc;
     607         pid_t pid;
     608609if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
     610if (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操作。

    复制代码
    622for (j = 0; j < server.saveparamslen; j++) {
     623struct saveparam *sp = server.saveparams+j;
     624625if (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);
     630break;
     631             }
     632          }      
     633634/* Trigger an AOF rewrite if needed */635if (server.bgsavechildpid == -1 &&
     636              server.bgrewritechildpid == -1 &&
     637              server.auto_aofrewrite_perc &&
     638              server.appendonly_current_size > server.auto_aofrewrite_min_size)
     639          {
     640longlongbase = server.auto_aofrewrite_base_size ?
     641                             server.auto_aofrewrite_base_size : 1;
     642longlong growth = (server.appendonly_current_size*100/base) - 100;
     643if (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后重新调用这个例程的执行。

  • 相关阅读:
    Python 函数与函数式编程
    Python 字符编码与转码
    Python 读写txt文件操作
    两阶段事务总结
    MPPDB集群高可用设计
    MPPDB中的各个组件
    IntelliJ IDEA2016学习小结
    mysql免安装版配置
    理想的智能机
    java对象的大小
  • 原文地址:https://www.cnblogs.com/lsx1993/p/4632383.html
Copyright © 2020-2023  润新知