《Redis設計與實現》學習筆記-服務端

Redis服務器負責與多個客戶端建立連接,處理客戶端請求,保存各個數據庫狀態。通過使用由I/O多路複用技術實現的事件處理器,Redis服務器採用單線程單進程處理客戶端命令請求。Redis通過redisServer結構來記錄服務端的各種狀態。

命令請求執行過程

1、客戶端發送命令請求,客戶端將命令請求轉換成協議格式。
2、服務端讀取命令請求,將命令請求緩存在客戶端輸入緩衝區中,對輸入緩衝區中的命令進行分析把參數和參數個數分別保存到客戶端狀態的argv屬性和argc屬性中,然後調用命令執行器執行指定的命令。
3、命令執行器根據argv[0]參數在命令表中查找參數所指定的命令,並將找到的命令保存到客戶端狀態cmd屬性中
4、執行預備操作:

  •  檢查客戶端cmd指針是否指向NULL,如果是返回一個錯誤;
  •  根據客戶端cmd屬性指向的redisCommand結構的arity屬性,檢查命令請求所給定的參數個數是否正確,如果參數個數不正確返回錯誤;
  •  檢查客戶端是否通過了身份驗證,未通過身份驗證的客戶端只能執行AUTH命令,如果未通過身份驗證的客戶端執行了除AUTH之外的其它命令,返回錯誤;
  •  如果服務器打開了maxmemory開關功能,那麼在執行命令前,先檢查服務器的內存佔用情況,並在有需要時進行內存回收,從而使得接下來的命令能夠順利執行;
  •  如果服務器上一次執行BGSAVE命令時出錯,並且服務器打開了stop-writes-on-bgsave-error功能,而且服務器將要執行的是一個寫命令,那麼服務器將拒絕執行這個命令,並向客戶端返回一個錯誤;
  •  當客戶端當前正在用SUBSCRIBE命令訂閱頻道,或者正在用PSUBSCRIBE命令訂閱模式,那麼服務器只會執行  客戶端發來的SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE四個命令,其它命令都會被拒絕;          
  •  如果服務器正在進行數據載入,那麼客戶端發送的命令必須帶有1標識(比如INFO、SHUTDOWN、PUBLISH)  纔會被服務器執行,其它命令都會被服務器拒絕;
  •  如果服務器因爲執行Lua腳本而超時並進入阻塞狀態,那麼服務器只會執行客戶端發來的SHUTDOWN nosave命令和SCRIPT KILL命令,其它命令都會被服務器拒絕;
  •  如果客戶端正在執行事務,那麼服務器只會執行客戶端發來的EXEC、DISCARD、MULTI、WATCH四個命令,其它命令都會被放進事務隊列中;
  •  如果服務器打開了監視器功能,那麼服務器會將要執行的命令和參數等信息發送給監視器。
當完成了以上預備操作之後,服務器就可以開始真正執行命令了。
5、調用命令的實現函數,redisCommand中有個proc屬性,它是一個函數指針,通過調用它來執行最終的命令實現。
6、執行後續工作:
  •  如果服務器開啓了慢日誌查詢日誌功能,那麼慢查詢日誌模塊會檢查是否需要爲剛剛執行完的命令請求添加一條新的慢查詢日誌;
  •  根據執行命令所耗費的時長,更新被執行命令的redisCommand結構的milliseconds屬性,並它的calls計數器值增1;
  •  如果服務器開啓了AOF持久化功能,那麼AOF持久化模塊會將剛剛執行的命令請求寫入到AOF緩衝區裏面;
  •  如果有其它從服務器正在複製當前服務器,那麼服務器將剛剛執行的命令傳播給所有從服務器;
7、將命令回覆發送給客戶端,命令實現函數會將命令回覆保存到客戶端的輸出緩衝區裏面,併爲客戶端的套接字關聯命令回覆處理器,當客戶端套接字變爲可寫狀態時,服務器將會執行命令回覆處理器,將保存在客戶端緩衝區中的命令回覆發送個客戶端。當命令發送完畢後,回覆處理器清空客戶端輸出緩衝區。(有個疑問,假設客戶端沒有嘗試讀取命令回覆,那麼服務端該如何處理該客戶端的輸出緩衝區?是在處理下個命令時被沖掉麼?)。

serverCron函數

服務器每個100毫秒執行一次serverCron函數,通常情況下,該函數時redis服務器唯一的定時任務,它的主要職責如下:
1、更新服務器時間緩存,redisServer中有unixtime和mstime兩個屬性,由於100毫秒才調用一次,所以這兩個屬性保存的時間都是不精確的,如果要獲取精確的系統時間,必須執行系統調用獲取。
2、更新LRU時鐘,redisServer中的lruclock屬性保存了服務器的LRU時鐘,它也是個服務器時間緩存,當服務器要計算一個數據庫鍵的空轉時間時,通過服務器的lruclock減redisObject的lru屬性獲取。serverCron默認以每10秒一次的頻率更新lruclock,所以它也是一個估算值。
3、更新服務器每秒執行命令數。
4、更新服務器內存峯值,記錄在redisServer的stat_peak_memory屬性中。
5、處理SIGTERM信號,在啓動服務器時,Redis會爲服務器進程的SIGTERM信號關聯處理器sigtermHandler函數,這個信號處理器負責在服務器接到SIGTERM信號時,打開服務器狀態的shutdown_asap標識,每次serverCron函數運行時,程序會對服務器狀態的shutdown_asap屬性進行檢查,並根據屬性的值決定是否關閉服務器,在關閉服務器前服務器會進行RDB持久化操作,這就是服務器攔截SIGTERM信號的原因。
6、管理客戶端資源,serverCron函數每次執行都會調用clientsCron函數, clientsCron函數會對一定數量的客戶端進行一下兩個檢查:

  • 如果客戶端與服務器之間連接已經超時(很長時間沒有互動),那麼程序釋放這個客戶端,關閉連接。
  • 如果客戶端在上一次執行命令請求之後,輸入緩衝區的大小超過了一定的長度,那麼程序會釋放客戶端當前的輸入緩衝區,並重新創建一個默認大小的輸入緩衝區,從而防止客戶端的輸入緩衝區耗費了過多的內存。
7、管理數據庫資源,調用databaseCron函數,對服務器中的一部分數據庫進行檢查,刪除其中的過期鍵,並在有需要時,對字典進行收縮操作。
8、執行被延遲的BGREWRITEAOF,服務器在執行BGSAVE期間,如果客戶端向服務器請求了BGREWRITEAOF命令,那麼服務器會將BGREWRITEAOF命令延遲到BGSAVE執行完畢之後執行,通過redisServer中的aof_rewrite_scheduled記錄(值爲1表示有BGREWRITEAOF命令被延遲了)。
9、檢查持久化操作的運行狀態,服務器通過redisServer的rdb_child_pid和aof_child_pid屬性記錄執行BGSAVE和BGREWRITEAOF命令的子進程id,這個兩個屬性也可用於檢查當前是否有BGSAVE或BGREWRITEAOF命令正在執行,每次serverCron執行時都會檢查者兩個屬性的值,只要有一個值不爲-1,程序就會執行一次wait3函數,檢查子進程是否有信號發來服務器:
  •  如果有信號達到,表示新的RDB文件已經生成或者AOF文件重寫已經完畢,服務器需要進行相應的後續操作,比如用新的RDB文件替換現有的RDB文件,或者用重寫後的AOF文件替換現有的AOF文件
  •  如果沒有信號到達,那麼表示持久化操作未完成,不做動作。
 如果rdb_child_pid和aof_child_pid兩個屬性的值都爲-1,那麼表示服務器沒有在進行持久化操作,這種情況下,程序執行三個檢查:
  •  查看是否有BGREWRITEAOF被延遲,如果有,執行一次新的BGREWRITEAOF。
  •  查看自動保存條件是否滿足,如果滿足並且服務器沒有執行其他持久化操作,那麼服務器開始一次新的BGSAVE。
  •  檢查AOF重寫條件是否滿足,如果滿足並且服務器沒有執行其他的持久化操作,執行一次新的BGREWRITEAOF命令。
10、將AOF緩衝區中的內容寫入AOF文件,如果開啓了AOF持久化功能,並且AOF緩衝區還有待寫入的數據,那麼serverCron會調用相關的函數將AOF緩衝區中的內容寫入到AOF文件中
11、關閉異步客戶端,服務器會關閉那些輸出緩衝區超出限制的客戶端。
12、增加cronloops計數器的值,這個值記錄了serverCron函數被執行了多少次,這個屬性會用來“每執行N次serverCron函數就執行一次指定代碼”的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章