前言
參考資料:《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 讀取命令請求
- 讀取套接字中協議格式的命令請求,並將其儲存到客戶端狀態的輸入緩衝區裡面;
- 分析協議格式的命令請求,提取引數命令以及引數個數,存進客戶端狀態的
argv
與argc
屬性; - 呼叫命令執行器,執行客戶端指定命令;
1.3 命令執行器(1):查詢命令實現
- 根據客戶端的
argv[0]
引數,在命令表中查詢引數所指定的命令,並將找到的命令redisCommand
儲存到客戶端狀態的cmd
屬性裡;
redisCommand
的主要引數:name
(名字),proc
(函式指標),arity
(命令引數個數),sflags
(命令屬性標識),flags
(sflags的二進位制標識),calls
(伺服器總共執行了多少次這個命令),milliseconds
(執行該命令的總耗時);
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
;