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 函數。函數
該函數的大的流程,按照代碼中的執行順序,咱們先了解下:
註冊一個watchdog,註冊方式是經過一個timer,註冊了該timer以後,會按期給當前進程,觸發一個SIGALRM
信號,觸發了這個信號後,會幹嗎呢,會回調位於 debug.c 文件中的 watchdogSignalHandler
方法,這個方法,主要是在redis執行一些命令時,超過指定時長後,打印一些debug日誌。
能夠參考:
更新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中。
計算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 = 一段時間內的操做數量/ 時間長度。
刷新服務器的 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; } }
網上的一篇文章寫得不錯,能夠參考:
記錄服務器的內存峯值
/* 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命令裏看到使用了。
判斷服務器的關閉標識是否打開,如打開,則關閉
// 服務器進程收到 SIGTERM 信號,關閉服務器 if (server.shutdown_asap) { // 嘗試關閉服務器 if (prepareForShutdown(0) == REDIS_OK) exit(0); }
打印數據庫的鍵值對信息、客戶端信息
單純的log操做,惟一注意的是,要把日誌級別調到REDIS_VERBOSE
纔看獲得
檢查客戶端空閒時長,關閉空閒超時的客戶端
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; } ... }
對數據庫執行各類操做
/* 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調整(若是字典的使用率低,會縮小其佔用的內存大小)
後續會詳解這部分。
若是當前沒有aof或者rdb後臺任務正在執行,且server以前被schedule了一個aof rewrite後臺任務,則執行
aof 重寫。(aof記錄了每一條命令,時間長了,會重複,好比先把key a設爲1,再設爲2,再設爲3,這樣,aof中有3條記錄,實際上,只須要一條便可,因此會重寫)
aof 重寫在一個子進程中進行,子進程完成後,會給當前進程發送信號,因此,當前進程會一直等待信號,等待子進程完成後,本身再作些處理。
好比,主進程要作什麼處理呢?在 aof 重寫期間,主進程可能仍是要不斷地處理命令(這裏不會無限期等待,此次等不到就到下一次週期任務時再等),這期間,處理的命令,不能記錄到aof文件中,省得影響正在進行aof 重寫的子進程,因此,主進程會把這期間的命令,記錄到一個小本本上。
等到子進程寫完了,主進程再把小本本上的aof命令,寫到aof日誌文件裏。
若是當前沒有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
根據配置的aof fsync策略,決定是否要刷新到文件中
前面咱們說的aof寫日誌文件,不必定真的就寫入了文件,可能還在OS cache中,須要調用 fsync 才能寫入到文件中。
這裏即對應配置文件中的:
# appendfsync always appendfsync everysec # appendfsync no
默認每秒執行一次fsync,性能和數據安全性的折衷。
涉及slave、cluster、sentinel的部分操做
若是運行在以上幾種模式下,會涉及到對應的一些週期操做,後續再涉及這塊。
本講的主題大概是這些,其中,細節部分,好比數據庫的週期任務等,留待下講繼續。