Redis學習筆記(十一) 伺服器

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設計與實現》,僅在此做學習與大家分享。

 

 

 

 

 

 

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 程式初始化以及運行過程服務端做了那些事情,著手準備!

 

—- end —-

 

每天學一點,總會有收穫。

 

說明:尊重作者知識產權,文中內容參考《Redis設計與實現》,僅在此做學習與大家分享。

 

Tags: