曹工說Redis源碼(7)-- redis server 的週期執行任務,到底要作些啥

文章導航

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 函數。函數

該函數的大的流程,按照代碼中的執行順序,咱們先了解下:

  1. 註冊一個watchdog,註冊方式是經過一個timer,註冊了該timer以後,會按期給當前進程,觸發一個SIGALRM信號,觸發了這個信號後,會幹嗎呢,會回調位於 debug.c 文件中的 watchdogSignalHandler方法,這個方法,主要是在redis執行一些命令時,超過指定時長後,打印一些debug日誌。

    能夠參考:

    Redis 2.6 的新特性:Watchdog(看門狗)

    Redis software watchdog

  2. 更新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中。

  3. 計算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 = 一段時間內的操做數量/ 時間長度。

  4. 刷新服務器的 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;
        }
    }

    網上的一篇文章寫得不錯,能夠參考:

    redis的LRU策略理解

  5. 記錄服務器的內存峯值

    /* 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命令裏看到使用了。

  6. 判斷服務器的關閉標識是否打開,如打開,則關閉

    // 服務器進程收到 SIGTERM 信號,關閉服務器
        if (server.shutdown_asap) {
            // 嘗試關閉服務器
            if (prepareForShutdown(0) == REDIS_OK) exit(0);
         }
  7. 打印數據庫的鍵值對信息、客戶端信息

    單純的log操做,惟一注意的是,要把日誌級別調到REDIS_VERBOSE纔看獲得

  8. 檢查客戶端空閒時長,關閉空閒超時的客戶端

    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;
        }
        ...
    }
  9. 對數據庫執行各類操做

    /* 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調整(若是字典的使用率低,會縮小其佔用的內存大小)

    後續會詳解這部分。

  10. 若是當前沒有aof或者rdb後臺任務正在執行,且server以前被schedule了一個aof rewrite後臺任務,則執行

    aof 重寫。(aof記錄了每一條命令,時間長了,會重複,好比先把key a設爲1,再設爲2,再設爲3,這樣,aof中有3條記錄,實際上,只須要一條便可,因此會重寫)

    aof 重寫在一個子進程中進行,子進程完成後,會給當前進程發送信號,因此,當前進程會一直等待信號,等待子進程完成後,本身再作些處理。

    好比,主進程要作什麼處理呢?在 aof 重寫期間,主進程可能仍是要不斷地處理命令(這裏不會無限期等待,此次等不到就到下一次週期任務時再等),這期間,處理的命令,不能記錄到aof文件中,省得影響正在進行aof 重寫的子進程,因此,主進程會把這期間的命令,記錄到一個小本本上。

    等到子進程寫完了,主進程再把小本本上的aof命令,寫到aof日誌文件裏。

  11. 若是當前沒有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
  12. 根據配置的aof fsync策略,決定是否要刷新到文件中

    前面咱們說的aof寫日誌文件,不必定真的就寫入了文件,可能還在OS cache中,須要調用 fsync 才能寫入到文件中。

    這裏即對應配置文件中的:

    # appendfsync always
    appendfsync everysec
    # appendfsync no

    默認每秒執行一次fsync,性能和數據安全性的折衷。

  13. 涉及slave、cluster、sentinel的部分操做

    若是運行在以上幾種模式下,會涉及到對應的一些週期操做,後續再涉及這塊。

總結

本講的主題大概是這些,其中,細節部分,好比數據庫的週期任務等,留待下講繼續。

相關文章
相關標籤/搜索