Redis服務器負責與多個客戶端建立網絡通信,處理客戶端發送的命令請求,在數據庫中保存客戶端執行命令所產生的數據,並通過資源管理來維持服務器自身的運轉。
命令請求過程(以set命令為例)
1、客戶端向服務器發送命令請求 SET KEY VALUE。
Redis服務器的命令請求來自於Redis客戶端,當用戶從客戶端鍵入一個命令請求時,客戶端會將這個命令命令請求請求轉換成協議格式,然后通過連接到服務器的套接字,將協議格式的命令請求發送給服務器。

2、服務器接收並處理客戶端發送來的命令請求 SET KEY VALUE,在數據庫中進行設置操作,並產生命令回復OK。
讀取套接字中的協議格式命令請求,並將其保存在客戶端狀態的輸入緩沖區中。
對輸入緩沖區中的命令請求進行解析,提取出命令請求中包含的命令參數,以及命令參數的格式,分別將參數和參數個數保存到客戶端狀態的argv和argc屬性中
調用命令執行器,執行客戶端指定的命令。
命令執行器:
1、查找命令。2、執行預備操作。(檢查命令、命令參數、客戶端身份驗證、檢查服務器內存占用情況、事務、服務器狀態、是否監聽等)3、調用命令實現函數,產生命令回復函數,保存在客戶端狀態的輸出緩存區4、執行后續工作:(慢查詢檢查記錄日志;根據命令耗時更新redisCommand結構的milliseconds屬性、calls計數器加1;如果開啟了AOF,如果是寫命令則寫入到AOF緩沖區;如果有其他從服務器正在復制當前的服務器,那么服務器會將剛剛執行的命令傳播給所有從服務器。)
3、服務器將命令回復OK發送為客戶端。
當客戶端套接字變為可寫狀態時,服務器會執行命令回復處理器,將保存在客戶端緩沖區的命令回復發送給客戶端。
4、客戶端接收服務器返回的命令回復OK,並將這個回復打印給用戶觀看。客戶端收到協議格式的命令回復后,將其轉換為人類可讀的格式,並打印。

serverCron函數
Redis服務器中的serverCron函數每100毫秒執行一次,這個函數負責管理服務器的資源,並保持服務器自身的良好運轉。
1、更新服務器緩存時間,為減少系統調用獲取當前時間的次數,服務器狀態中的unixtime和mstime屬性被用作當前時間的緩存:
struct redisServer{ //保存了秒級精度的當前unix時間戳 time_t unixtime; //保存了毫秒級精度的系統當前unix時間戳 long long mstime; //默認每10秒更新一次事件緩存,用於計算鍵的空轉時間。 unsigned lruclock:22 }
服務器只會在打印日志、更新服務器LRU始終、決定是否執行持久化任務、計算服務器上線時間這類對事件精度不高的功能上用;對於為鍵設置過期事件、添加慢日志這種需要高精度事件的功能來說,服務器還是會再次執行系統調用,從而獲得更准確的系統當前事件。
每個Redis對象都有一個lru屬性,這個lru屬性保存了對象最后一次被命令訪問的時間:
typedef struct redisObject{ unsigned lru:22' } robj;
serverCron函數中的trackOperationsPerSecond函數會以每100毫秒一次的頻率執行,這個函數的功能以抽樣計算的方式,估算並記錄服務器在最近一秒鍾處理的命令請求數量,這個值通過INFO status命令的instantaneous_ops_per_sec域查看。
2、更新服務器內存峰值記錄:服務器狀態中的stat_peak_memory屬性記錄服務器的內存峰值大小:
struct redisServer{ //已使用內存峰值 size_t stat_peak_memory; }
3、處理sigterm信號
static void sigtermHandler(int sig){ //打印日志 redisLogFromHandler(REDIS_WARNING,"received sigterm,scheduling shutdown..."); //打開關閉標識 server.shutdown_asap=1; }
struct redisServer{ //serverCron函數運行時,程序灰度服務器狀態的shutdown_asap屬性進行檢查,並根據屬性值決定是否關閉服務器。 //關閉服務器的標識 //值為1時,關閉服務器 //值為0時,不做動作 int shutdown_asap; }
4、管理客戶端資源
serverCron函數每次執行都會調用clientCron函數,clientsCron函數會對一定數量的客戶端進行檢查:如果客戶端與服務器之間的連接已經超時,那么程序釋放這個客戶端;如果客戶端在上一次執行命令請求后,輸入緩沖區的大小超過了一定的長度,那么程序會釋放客戶端當前的輸入緩沖區,重新創建一個迷人大小的輸入緩沖區,防止客戶端的輸入緩沖區耗費過多的資源。
5、管理數據庫資源 :刪除過期鍵,如有需要,對字典進行收縮操作。
6、執行被延遲的BGREWRITEAOF
如果BGSAVE執行期間,客戶端發來BGREWRITEAOF命令,則需要延遲到BGSAVE命令執行完成后。
7、檢查持久化操作運行狀態
服務器狀態使用rdb_child_pid\aof_child_pid屬性記錄執行BGSAVe命令和BGREWRITEAOF命令的子進程ID,用於檢查命令是否正在執行
struct redisServer{ //記錄執行BGSAVE命令的紫禁城,如果沒有執行則為-1 pid_t rdb_child_pid; //記錄執行BGREWRITEAOF命令的子進程ID,沒執行則為-1 pid_t aof_child_pid; }

8、將AOF緩沖區中的內容寫入AOF文件
9、關閉異步客戶端(檢查輸出緩沖區大小)
10、增加cronloops計數器的值(沒執行serverCron函數N次就執行一次指定的代碼)
初始化服務器
1、初始化服務器狀態結構,創建一個struct redisServer類型的實例變量server作為服務器的狀態。
initServerConfig函數完成的主要工作:
設置服務器運行ID
設置服務器的默認運行頻率
設置服務器的默認配置文件路徑
設置服務器的運行架構
設置服務器的默認端口號
設置服務器默認RDB持久化條件和APF持久化條件
初始化服務器LRU時鍾
創建命令表
2、載入配置項修改默認的配置。
3、初始化服務器數據結構
在執行initServerConfig函數初始化server狀態時,程序只創建命令表一個數據結構,服務器在次數初始化其他數據結構:
server.clients鏈表,記錄所有與服務器相連接的客戶端狀態
erver.db數組,包含服務器的所有數據庫。
保存頻道訂閱信息的server.pubsub_channels字典以及保存模式訂閱信息的server_publsub_patterns鏈表。
執行Lua腳本的Lua環境 server.lua;
用於保存慢查詢日志的server.slwlog鏈表
除了初始化數據結構之外,initServer還進行了一些非常重要的設置操作:
為服務器設置進程信號處理器。
創建共享對象。
打開服務器的監聽端,並未監聽套接字關聯連接應答事件處理時,等待服務器正式運行時接收客戶端連接。
為serverCron函數創建時間事件,等待服務器正式運行時,執行serverCron函數。
如果AOF持久化功能打開,那么打開現有的AOF文件,如果文件不存在則創建新的AOF文件
初始化服務器的后台I/O模塊,為將來的I/O操作做准備。
執行完以上內容那么你就看到了熟悉的畫面:

4、還原數據庫裝填
在完成對服務器狀態server變量的初始化后,服務器需要載入RDB文件或者AOF文件,並根據文件記錄的內容還原數據庫狀態
5、執行事件循環
到此服務器的初始化工作圓滿完成,服務器現在開始可以接收客戶端的連接請求,並處理客戶端發來的命令請求。
寫到這里,非常有沖動寫一下.net 程序初始化以及運行過程服務端做了那些事情,着手准備!
每天學一點,總會有收獲。
說明:尊重作者知識產權,文中內容參考《Redis設計與實現》,僅在此做學習與大家分享。

