初始化服務器
從啟動 Redis 服務器,到服務器可以接受外來客戶端的網絡連接這段時間,Redis 需要執行一系列初始化操作。
整個初始化過程可以分為以下六個步驟:
- 初始化服務器全局狀態。
- 載入配置文件。
- 創建 daemon 進程。
- 初始化服務器功能模塊。
- 載入數據。
- 開始事件循環
初始化服務器全局狀態
redis.h/redisServer結構記錄了和服務器相關的所有數據,這個結構主要包含以下信息:
- 服務器中的所有數據庫。
- 命令表:
- 在執行命令時,根據字符來查找相應命令的實現函數。
- 事件狀態。
- 服務器的網絡連接信息:
- 套接字地址
- 端口,以及套接字描述符。
- 所有已連接客戶端的信息。
- Lua腳本的運行環境及相關選項。
- 實現訂閱與發布(pub/sub)功能所需的數據結構。
- 日志(log)和慢查詢日志(slowlog)的選項和相關信息。
- 數據持久化(AOF和RDB)的配置和狀態。
- 服務器配置選項:
- 比如要創建多少個數據庫,
- 是否將服務器進程作為daemon進程來運行,
- 最大連接多少個客戶端,
- 壓縮結構(zip structure)的實體數量,等等。
- 統計信息:比如鍵有多少次命令、不命中,服務器的運行時間,內存占用,等等。
上述過程只包含=Redis服務器單機信息,不包含sentinel,cluster等功能。
當 server 變量的初始化完成之后,程序進入服務器初始化的下一步:讀入配置文件。
載入配置文件
在初始化服務器的上一步中,程序為server變量(也即是服務器狀態)的各個屬性設置了默
認值,但這些默認值有時候並不是最合適的:
- 用戶可能想使用 AOF 持久化,而不是默認的 RDB 持久化。
- 用戶可能想用其他端口來運行Redis,以避免端口沖突。
- 用戶可能不想使用默認的16個數據庫,而是分配更多或更少數量的數據庫。
- 用戶可能想對默認的內存限制措施和回收策略做調整。
創建daemon進程
Redis默認以daemon進程的方式運行。
當服務器初始化進行到這一步時,程序將創建 daemon 進程來運行 Redis ,並創建相應的 pid文件。
初始化服務器功能模塊
在這一步,初始化程序完成兩件事:
- 為server變量的數據結構子屬性分配內存。
- 為數據結構分配內存,並初始化這些數據結構,等同於對相應的功能進行初始化
- 比如說,當為訂閱與發布所需的鏈表分配內存之后,訂閱與發布功能就處於就緒狀態,隨時可以為 Redis 所用了。
- 初始化這些數據結構。
在這一步,程序完成的主要動作如下:
- 初始化 Redis 進程的信號功能。
- 初始化日志功能。
- 初始化客戶端功能。
- 初始化共享對象。
- 初始化事件功能。
- 初始化數據庫。
- 初始化網絡連接。
- 初始化訂閱與發布功能。
- 初始化各個統計變量。
- 關聯服務器常規操作(cron job)到時間事件,關聯客戶端應答處理器到文件事件。
- 如果AOF功能已打開,那么打開或創建AOF文件。
- 設置內存限制。
- 初始化Lua腳本環境。
- 初始化慢查詢功能。
- 初始化后台操作線程。
完成這一步之后,服務器打印出 Redis 的 ASCII LOGO 、服務器版本等信息,表示所有功能
模塊已經就緒,可以等待被使用了:
雖然所有功能已經就緒,但這時服務器的數據庫還是一片空白,程序還需要將服務器上一次執
行時記錄的數據載入到當前服務器中,服務器的初始化才算真正完成。
載入數據
在這一步,程序需要將持久化在 RDB 或者 AOF 文件里的數據,載入到服務器進程里面。
如果服務器有啟用AOF功能的話,那么使用AOF文件來還原數據;
否則,程序使用RDB文件來還原數據。
當執行完這一步時,服務器打印出一段載入完成信息:
[6717] 22 Feb 11:59:14.830 * DB loaded from disk: 0.068 seconds
開始事件循環
到了這一步,服務器的初始化已經完成,程序打開事件循環,開始接受客戶端連接。
以下是初始化完成之后,服務器狀態和各個模塊之間的關系圖:


客戶端連接到服務器
當 Redis 服務器完成初始化之后,它就准備好可以接受外來客戶端的連接了。
當一個客戶端通過套接字函數 connect 到服務器時,服務器執行以下步驟:
- 服務器通過文件事件無阻塞地 accept 客戶端連接,並返回一個套接字描述符 fd 。
- 服務器為 fd 創建一個對應的 redis.h/redisClient 結構實例,並將該實例加入到服務器的已連接客戶端的鏈表中。
- 服務器在事件處理器為該 fd 關聯讀文件事件。
完成這三步之后,服務器就可以等待客戶端發來命令請求了。
Redis 以多路復用的方式來處理多個客戶端,為了讓多個客戶端之間獨立分開、不互相干擾,
服務器為每個已連接客戶端維持一個 redisClient 結構,從而單獨保存該客戶端的狀態信息。
redisClient 結構主要包含以下信息:
- 套接字描述符。
- 客戶端正在使用的數據庫指針和數據庫號碼。
- 客戶端的查詢緩存( query buffer)和回復緩存( reply buffer)。
- 一個指向命令函數的指針,以及字符串形式的命令、命令參數和命令個數,這些屬性會在命令執行時使用。
- 客戶端狀態:
- 記錄了客戶端是否處於
- SLAVE
- MONITOR
- 或者事務狀態。
- 實現事務功能(比如 MULTI 和 WATCH)所需的數據結構。
- 實現阻塞功能(比如 BLPOP 和 BRPOPLPUSH)所需的數據結構。
- 實現訂閱與發布功能(比如PUBLISH和SUBSCRIBE)所需的數據結構。
- 統計數據和選項:客戶端創建的時間,客戶端和服務器最后交互的時間,緩存的大小,等等。
注意:上面列出的客戶端結構信息不包含復制相關屬性;
命令的請求,處理和結果返回
當客戶端連上服務器之后,客戶端就可以向服務器發送命令請求了。
從客戶端發送命令請求,到命令被服務器處理、並將結果返回客戶端,整個過程有以下步驟:
- 客戶端通過套接字向服務器傳送命令協議數據。
- 服務器通過讀事件來處理傳入數據,並將數據保存在客戶端對應 redisClient 結構的查詢緩存中。
- 根據客戶端查詢緩存中的內容,程序從命令表中查找相應命令的實現函數。
- 程序執行命令的實現函數,修改服務器的全局狀態 server 變量,並將命令的執行結果保存到客戶端 redisClient 結構的回復緩存中,然后為該客戶端的 fd 關聯寫事件。
- 當客戶端 fd 的寫事件就緒時,將回復緩存中的命令結果傳回給客戶端。至此,命令執行完畢。
命令請求實例:set 的執行過程
假設現在客戶端 C1 是連接到服務器 S 的一個客戶端;
當用戶執行命令 SET YEAR 2013 時 過程:
- 客戶端調用寫入函數,將協議內容 寫入連接到服務器的套接字中。
- 協議內容:*3\r\n$3\r\nSET\r\n$4\r\nYEAR\r\n$4\r\n2013\r\n"
- 當S的文件事件處理器執行時, 它會察覺到 C1 所對應的讀事件已經就緒,於是它將協議文本讀入,並保存在查詢緩存。
- 通過對查詢緩存進行分析( parse)
- 服務器在命令表中查找 SET 字符串所對應的命令實現函數,最終定位到 t_string.c/setCommand 函數,
- 另外,兩個命令參數 YEAR 和 2013 也會以字符串的形式保存在客戶端結構中。
- 接着,程序將客戶端、要執行的命令、命令參數等送入命令執行器:
- 執行器調用 setCommand函數,將數據庫中 YEAR 鍵的值修改為 2013
- 然后將命令的執行結果保存在客戶端的回復緩存中
- 並為客戶端 fd 關聯寫事件,用於將結果回寫給客戶端
- 因為 YEAR 鍵的修改,其他和數據庫命名空間相關程序
- 比如 AOF 、 REPLICATION 還有事務安全性檢查(是否修改了被 WATCH 監視的鍵?)也會被觸發
- 當這些后續程序也執行完畢之后,命令執行器退出,服務器其他程序(比如時間事件處理器)繼續運行。
- 當 C1 對應的寫事件就緒時,程序就會將保存在客戶端結構回復緩存中的數據回寫給客戶端
- 當客戶端接收到數據之后,它就將結果打印出來,顯示給用戶看
小結
• 服務器經過初始化之后,才能開始接受命令。
• 服務器初始化可以分為六個步驟:
• 服務器經過初始化之后,才能開始接受命令。
• 服務器初始化可以分為六個步驟:
1. 初始化服務器全局狀態。
2. 載入配置文件。
3. 創建 daemon 進程。
4. 初始化服務器功能模塊。
5. 載入數據。
6. 開始事件循環。
• 服務器為每個已連接的客戶端維持一個客戶端結構,這個結構保存了這個客戶端的所有
狀態信息。
• 客戶端向服務器發送命令,服務器接受命令然后將命令傳給命令執行器,執行器執行給
狀態信息。
• 客戶端向服務器發送命令,服務器接受命令然后將命令傳給命令執行器,執行器執行給
定命令的實現函數,執行完成之后,將結果保存在緩存,最后回傳給客戶端。