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后重新調用這個例程的執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM