Redis | 第7章 Redis 伺服器《Redis設計與實現》


前言

參考資料:《Redis設計與實現 第二版》;

第二部分為單機資料庫的實現,主要由以下模組組成:資料庫持久化事件客戶端伺服器

本篇將介紹 Redis 的伺服器端,從伺服器接收客戶端的命令請求serverCron 函數以及初始化伺服器三個角度介紹;


1. 命令請求的執行過程

1.1 發送命令請求

  • 用戶在客戶端鍵入一個命令請求 { SET KEY VALUE };
  • 客戶端將命令請求轉換成協議格式 { *3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n };
  • 通過連接到伺服器的套接字,將協議格式的命令請求發送給伺服器;

發送命令請求過程

1.2 讀取命令請求

  • 讀取套接字中協議格式的命令請求,並將其保存到客戶端狀態的輸入緩衝區裡面;
  • 分析協議格式的命令請求,提取參數命令以及參數個數,存進客戶端狀態的 argvargc 屬性;
  • 調用命令執行器,執行客戶端指定命令;
    讀取命令請求
    解析出的協議格式內容

1.3 命令執行器(1):查找命令實現

  • 根據客戶端的 argv[0] 參數,在命令表中查找參數所指定的命令,並將找到的命令 redisCommand 保存到客戶端狀態的 cmd 屬性里;

命令表示例

設置客戶端狀態的 cmd 指針

  • redisCommand 的主要參數:name(名字),proc(函數指針),arity(命令參數個數),sflags(命令屬性標識),flags(sflags的二進位標識),calls(伺服器總共執行了多少次這個命令),milliseconds(執行該命令的總耗時);

sflags屬性的標識

1.4 命令執行器(2):執行預備操作

  • 檢查客戶端狀態的 cmd 指針是否指向 NULL,是則返回錯誤;

  • 根據 cmd 指向的 redisCommand 結構的 arity 屬性檢查參數個數是否正確,不正確則返回錯誤;

  • 檢查客戶端是否通過身份驗證,未通過的只能執行 AUTH 命令;

  • 若伺服器打開了 maxmemory 功能,則在執行命令之前先檢查伺服器記憶體佔用情況,在需要時回收記憶體。記憶體回收失敗返回錯誤;

  • 等等……

1.5 命令執行器(3):調用命令的實現函數

  • 執行以下語句:client->cmd->proc(client)
  • 被調用的命令實現函數執行指定操作,返回相應命令回復;
    執行前客戶端狀態

執行後客戶端狀態

1.6 命令執行器(4):執行後續工作

  • 如果伺服器開啟了慢查詢功能,慢查詢日誌模組會檢查是否需要為剛剛執行完的命令請求添加一條新的慢查詢日誌;
  • 根據執行命令耗費時長更新 redisCommand 結構的 milliseconds 屬性,同時將 calls 屬性加一;
  • 如果伺服器開啟了 AOF 持久化功能,會將命令寫入 AOF 緩衝期里;
  • 如果有從伺服器正則複製當前伺服器,伺服器會將命令傳播給所有從伺服器;

1.7 將命令回複發送給客戶端

  • 當客戶端套接字變為可寫狀態時,伺服器會執行命令回復處理器,將保存在客戶端輸出緩衝區中的命令回複發送給客戶端 {+OK\r\n};

  • 當命令發送完畢後,回復處理器會清空客戶端狀態的輸出緩衝區,為處理下一個命令請求最準備;

1.8 客戶端接收並列印命令回復

  • 客戶端接收協議格式的命令回復後,會將回復轉成易讀格式;

客戶端接收並列印命令回復

2. serverCron 函數

  • serverCron 函數默認每隔 100ms 執行一次,負責管理伺服器資源與保持伺服器自身良好運轉;

  • 下面是 serverCron 函數所做操作的介紹:

    • 更新伺服器時間快取:更新伺服器狀態的 unixtime(秒級) 屬性和 mstime(毫秒級) 屬性。精度不高,用於:列印日誌、更新 LRU 時鐘、決定是否執行持久化任務、伺服器上線時間等;

    • 更新 LRU 時鐘:更新伺服器狀態的 lruclock(10秒更新一次) 和 lru 屬性。前者用於計算鍵的空轉時間,後者保存了對象最後一次被命令訪問的時間。空轉時間=lruclock-lru;

    • 更新伺服器每秒執行命令次數:調用 trackOperationsPerSecond 函數。以抽樣計算的方式,估算並記錄伺服器在最近 1s 處理的命令請求數量;

    • 更新伺服器記憶體峰值函數:更新stat_peak_memory 屬性。該屬性記錄了伺服器的記憶體峰值大小;

    • 處理 SIGTERM 訊號:在啟動伺服器時,Redis 會為伺服器進程的 SIGTERM 訊號關聯處理器 sigtermHandler 函數,訊號處理器負責在伺服器接到 SIGTERM 訊號時,打開伺服器狀態的 shutdown_asap 標識;

    • 管理客戶端資源:調用 clientsCron 函數。如果客戶端與伺服器之間的連接已經超時,則釋放客戶端。如果客戶端輸入緩衝區大小超過一定長度,則釋放客戶端當前的輸入緩衝區,並創建一個默認大小的輸入緩衝區;

    • 管理資料庫資源:調用 databasesCron 函數,刪除過期鍵,對字典進行收縮操作;

    • 執行被延遲的 BGREWRITEAOF:檢查在執行 BGSAVE 命令期間,是否有 BGREWRITEAOF 命令被延遲執行;

    • 檢查持久化操作的運行狀態:檢查 rdb_child_pid(記錄 BGSAVE 命令的子進程 ID) 屬性與 aof_child_pid(記錄執行 BGREWRITEAOF 命令的子進程 ID) 屬性。值為 -1 說明伺服器沒有進行持久化操作;

持久化檢查過程

  • 將 AOF 緩衝區中的內容寫入 AOF 文件:如果服務開啟 AOF 功能,並且 AOF 緩衝區里有待寫入數據,則將 AOF 緩衝區中的內容寫入 AOF 文件里;

  • 關閉非同步客戶端:關閉輸出緩衝區大小超過限制的客戶端;

  • 增加 cronloops 計數器的值:對伺服器狀態的 cronloops 屬性增 1;

3. 初始化伺服器

3.1 初始化伺服器狀態結構

  • 即創建使用默認值一個 struct redisServer 結構體;
  • 負責初始化一般屬性;
  • 初始化的工作由 redis.c/initServerConfig 函數完成,該函數的主要工作有:
    • 設置伺服器的運行 ID;
    • 設置伺服器的默認運行頻率;
    • 設置伺服器的默認配置文件路徑;
    • 設置伺服器的運行架構;
    • 設置伺服器的默認埠號;
    • 設置伺服器的默認 RDB 持久化條件和 AOF 持久化條件;
    • 初始化伺服器的 LRU 時鐘;
    • 創建命令表;

3.2 載入配置選項

  • 載入用戶給定的配置參數和配置文件,並對 server 變數相關屬性的值進行修改;
  • 指定埠號:redis-server --port 10086
  • 修改資料庫數量與 RDB 文件壓縮功能:redis-server redis.conf。並且 redis.conf 文件里包含以下內容:
    # 將資料庫數量設置為32個
    database 32
    # 關閉 RDB 文件的壓縮功能
    rdbcompression no
    

3.3 初始化伺服器數據結構

  • 負責初始化數據結構

    • 調用 initServer 函數,初始化下列資料庫:
    • 設置資料庫:server.clients 鏈表(存客戶端)、server.db 數組(存資料庫)、server.pubsub_channels 字典(保存頻道訂閱資訊)、server.lua(用於執行 Lua 腳本的 Lua 環境)、server.slowlog(用於保存慢查詢日誌)
  • 進行一些重要設置

    • 為伺服器設置進程訊號處理器;
    • 創建共享對象(如 OK、整數 0-9999);
    • 打開伺服器的監聽埠,為監聽套接字關聯連接應答事件處理器,等待伺服器正式運行時接受客戶端的連接;
    • serverCron 函數創建時間事件;
    • 當 AOF 持久化功能打開時,打開現有 AOF 文件或創建並打開一個新的 AOF 文件,為 AOF 寫入做準備;
    • 初始化伺服器的後台 I/O 模組,為將來 I/O 操作做準備;

3.4 還原資料庫狀態

  • 載入 RDB 文件或 AOF 文件(優先),並根據文件記錄的內容還原伺服器的資料庫狀態;
  • 成功還原的日誌資訊:
    [8040] 01 Dec 20:12:41.758 * DB loaded from disk: 0.001 seconds

3.5 執行事件循環

  • 在列印下列日誌後執行事件循環(loop函數);
    [8040] 01 Dec 20:12:41.758 * The server is now ready to accept connections on port 6379


最後

新人製作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標註出處!