您好,登錄后才能下訂單哦!
這篇文章主要講解了“什么是Redis事件和服務器”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“什么是Redis事件和服務器”吧!
Redis事件和服務器
Redis是個單線程的,但是速度非常快,其主要原因是因為它是基于事件的,是一個事件驅動程序,了解NIO的應該都知道這種方式。
Redis服務器需要處理兩類事件。
文件事件(file event): Redis服務器通過套接字與客戶端進行連接,而**文件事件就是服務器對套接字操作的抽象。**服務器與客戶端的通信會產生相應的文件事件,而服務器通過監聽并處理這些事件來完成一系列網絡通信操作。
時間事件(file event): Redis服務器中的一些操作(比如serverCron函數)需要在給定的時間點執行,而時間事件就是服務器對這類定時操作的抽象。
Redis基于Reactor模式開發了自己的網絡事件處理器,則個處理器被稱為文件時間處理器(file event handler):
文件事件處理器采用I/O多路復用(multiplexing)程序來同時監聽多個套接字,并根據套接字目前嶄新的任務來為套接字關聯不同的事件處理器。
當被監聽的套接字準備好連接應答(accept),讀取(read),寫入(write),關閉(close)等操作時,與操作對應的文件事件就會產生,這是文件時就處理器就會調用套接字之前關聯號的事件處理器來處理這些事件。
文件時間處理器由四個部分組成,分別是套接字。I/O多路復用程序,文件事件分發器(dispatcher)和事件處理器。
文件事件是對套接字操作的抽象,每當一個套接字準備好執行連接應答,寫入,讀取,關閉等操作時,就會產生一個文件時間。因為一個服務器會連接多個套接字,所有文件事件可能并發出現。
I/O多路復用程序負責監聽多個套接字,并向文件事件分發器傳送那些產生了時間的套接字。I/O多路復用程序總是將所有產生時間的套接字放到一個隊列里面,然后通過這個隊列,以有序,同步,每次一個套接字的方式向文件時間分發器傳送套接字。
文件事件分發器接收I/O多路復用程序傳來的套接字,并根據套接字產生的時間類型,調用相應的事件處理器。
服務器會為執行不同的任務的套接字關聯不同的事件處理器,這些處理器是一個個函數,它們定義某個時間發生時,服務器應該執行的操作。
Tips: Redis的I/O多路復用程序的所有功能都是通過包裝常見的select,epoll,evport和kqueue這些I/O多路復用函數庫來實現的,并且為每個多路復用函數庫都實現了相同的API,因此底層是可以互換的。在編譯的時候,會自動選擇系統中性能最高的I/O多路復用函數庫來作為底層實現。
客戶端發起請求示例:
Redis的時間事件也有兩類:
定時事件: 讓一段程序在指定的時間之后執行一次。
周期性事件: 讓一段程序每隔指定時間就執行一次。
所有的時間事件都放在一個無序鏈表中,每當事件執行器運行時,它就遍歷整個鏈表,查找所有已到達的時間事件,并調用相應的事件處理器。(這里的無序是指事件到達時間無序)
serverCron函數負責定期對Redis的資源和狀態進行檢查和調整,主要工作包括:
更新服務器的各類統計信息,比如時間,內存占用,數據庫占用情況等。
清理數據庫中的過期鍵值對。
關閉和清理連接失效的客戶端。
嘗試進行AOF或RDB持久化操作。
如果是master,那么對從服務進行定期同步。
如果處于cluster,對集群進行定期同步和連接測試。
該函數默認執行時間是每秒10次,可以通過調整配置hz的值來改變,這個值代表的是每秒執行的次數哦!
hz 10
更新服務器時間緩存
Redis服務器中有不少功能要獲取系統當前時間,而每次獲取都需要執行一次系統調用,為了減少系統調用次數,服務器狀態中額unixtime屬性和mstime被用作當前時間緩存。
struct redisServer { // ... // 保存秒級進度的系統當前UNIX時間戳 time_t unixtime; // 保存毫秒級進度的系統當前UNIX時間戳 long long mstime; // ... };
因為函數每秒運行10次,100毫秒一次,所以這兩個屬性的精確度不高。
服務器只會在打印日志,更細那服務器的LRU時鐘,決定好似否執行持久化任務、計算服務器上線時間這類對時間精確度要求不高的功能上。
對于為鍵設置過期時間,添加慢查詢日志這種需要高精確度時間的功能來說,服務器還是會再次執行系統調用,從而獲得最準確的系統當前時間。
更新LRU時鐘
服務器狀態中的lruclock屬性保存了服務器的LRU始終,這個屬性也是服務器時間緩存的一種,用來計算對象空轉時長。
struct redisServer { // ... // 默認每秒更新一次 unsigned lruclock:22; //... }
每個redis對象有lru屬性,這個屬性保存了對象最后一次被命令訪問的時間。
空轉時間=lruclock - lru.
命令:
OBJECT IDLETIME key: 查看對象空轉時長 INFO server: 可以輸出server中lruclock的值
更新服務器每秒執行命令次數
redis> INFO stats # Stats ... instantaneous_ops_per_sec: 558 ...
以上命令結果顯示,在最近一秒之內,服務器大概處理了558個命令。
更新服務器內存峰值記錄
服務器stat_peak_memory屬性記錄了服務器內存峰值大小。
redis> INFO memory # Memory ... used_memory_peak:6180016 used_memory_peak_human:5.89M ...
以上命令查看內存峰值大小。
處理SIGTERM信號
啟動服務器時,Redis會為進程的SIGTERM信號關聯處理器(一個函數),這個信號處理器負責在服務器接收到SIGTERM信號時,打開服務器狀態的shutdown_asap標識。
每次serverCron函數運行時,程序都會對服務器狀態的shutdown_asap屬性進行檢查,如果值為1,則服務器會先進行RDB持久化操作,然后關閉服務器。
管理客戶端資源
主要是檢查客戶端:
如果客戶端與服務器之間連接已經超時,則釋放這個客戶端。
如果客戶端在上一次執行命令請求之后,輸入緩沖區的大小超過了一定長度,那么程序或釋放客戶端當前的輸入緩沖區,并重新創建一個默認大小的輸入緩沖區,從而防止客戶端你的輸入緩沖區耗費過多的內存。
管理數據庫資源
這個部分就主要是檢查服務器中的一部分數據庫,刪除其中的過期鍵,并在有需要時,對字典(hash表)進行收縮操作。
執行被延遲的BGREWRTEAOF
在服務器執行BGSAVE命令期間,如果客戶端向服務器發來BGREWRITEAOF命令,那么服務器會將BGREWRITEAOF命令的執行時間延遲到BGSAVE命令執行完畢之后。
服務器的aof_rewrite_scheduled屬性記錄了是否延遲了BGREWRITEAOF命令。
struct redisServer { // ... // 值為1表示有BGREWRITEAOF命令被延遲了 int aof_rewrite_scheduled; // ... }
serverCron命令運行時都會檢查BGSAVE和BGREWRITEAOF命令是否在執行,如果都沒有執行,那么檢查服務器的aof_rewrite_scheduled屬性,如果值為1,那么就執行延遲的BGREWRITEAOF命令.
檢查持久化操作的運行狀態
服務器狀態使用rdb_child_pid和aof_child_pid屬性記錄執行BGSAVE命令和BGREWRITEAOF命令的子進程ID,這兩個屬性也可以用于檢查對應命令是否正在執行:
struct redisServer { // ... // 記錄執行BGSAVE命令的子進程ID // 如果沒有執行,值為-1 pid_t rdb_child_pid; // 記錄執行BGREWRITEAOF命令的子進程ID // 如果沒有執行,值為-1 pid_t aof_child_pid; // ... }
serverCron函數每次執行時,程序都會檢查這兩個屬性的值,只要其中一個屬性不為-1,程序就會檢查子進程是否有信號發來服務器進程(調用其他函數)。
如果有信號到達,那么表示新的RDB文件已經生成完畢或者AOF文件已經重寫完畢,服務器需要進行后續操作,比如新的AOF替換舊的AOF文件。
如果沒有信號到達,表示持久化操作未完成,程序不做動作。
如果這兩個屬性的值都為-1,那么做三個檢查:
檢查是否有BGREWRITEAOF命令被延遲,如果有, 開始執行命令。
檢查自動保存條件是否滿足,如果滿足,且服務器當前沒有執行其他持久化操作,那么開始一次新的BGSAVE操作(因為上個檢查可能會引發一次新的BGREWRITEAOF,所有這次檢查中,程序會再次確認是否已經在執行持久化操作了)。
檢查服務器設置的AOF重寫條件是否滿足,如果條件滿足,并且服務器沒有執行其他持久化操作,那么開始一次新的BGREWRITEAOF操作(也會挨次確認,因為上兩個檢查都可能會引發新的持久化操作)。
圖解:
將AOF緩沖區的內容寫入AOF文件
如果服務器開啟了AOF持久化功能,并且AOF緩沖區里面還有待寫入的數據,那么serverCron函數會調用相應的程序,將AOF緩沖區中的內容寫入到AOF文件。
關閉異步客戶端
這一步,服務器會關閉輸出緩沖區大小超出限制的客戶端。
客戶端的輸入緩沖區用于把醋你客戶端發送的命令請求:
typedef struct redisClient { // ... sds querybuf; // ... }
這個緩沖區中保存的是協議的值。
set mKey mValue querybuf的值為 *3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n
輸入緩沖區的大小會根據內容動態的縮小或者擴大,但它的值不能超過1G,否則服務器將關閉這個客戶端。
增加cronloops計數器的值
服務器狀態的cronloops屬性記錄了serverCron函數執行的次數
struct redisServer { // ... // serverCron函數沒執行一次,值就加1 int cronloops; // ... }
這個屬性目前在服務器中的唯一作用,就是在復制模塊中實現沒執行N次就執行一次指定代碼的功能。
if (cronloops % n == 0) { // 指定代碼,這個指定的代碼應該是服務器處理的一些事了,每隔多少次就處理一次 }
命令請求的時候,在客戶端輸入命令的時候,客戶端會將這個命令轉換成協議,然后將協議內容發送給服務器。
*<參數數量> CR LF $<參數 1 的字節數量> CR LF <參數 1 的數據> CR LF ... $<參數 N 的字節數量> CR LF <參數 N 的數據> CR LF 注意:CRLF就是換行 \r\n
我們先來試試:
cmd: set mKey mValue protocal: *3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n
然后我們直接用java socket來發送協議給服務器。
public static void main(String[] args) throws Exception { Socket socket = new Socket("192.168.2.11", 16379); //獲取輸出流,向socket寫數據 OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream); printWriter.print("*3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n"); printWriter.flush(); //關閉輸出流 socket.shutdownOutput(); //讀取服務器返回的socket的數據 InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String info = ""; String temp = null; while ((temp = bufferedReader.readLine()) != null) { info += temp; System.out.println("客戶端接收服務端發送信息:" + info); } //關閉相對應的資源 bufferedReader.close(); inputStream.close(); printWriter.close(); outputStream.close(); socket.close(); }
執行結果:
客戶端接收服務端發送信息:+OK Process finished with exit code 0
上服務器檢查是否有值:
redis> get mKey "mValue"
第一步就是創建一個strct redisServer類型的實例變量作為服務器狀態,并為結構中的各個屬性設置默認值。主要包括以下部分:
設置服務器的運行ID。
設置服務器的默認運行頻率。
設置服務器的默認配置文件路徑。
設置服務器的運行架構。
設置服務器的默認端口號。
設置服務器的默認RDB持久化條件和AOF持久化條件。
初始化服務器的LRU時鐘。
創建命令表。
當初始化之后,就進入下一步,載入配置項。
在啟動服務器時,用戶可以通過給定配置參數或者指定配置文件來修改服務器的默認配置,比如
redis-server --port 16379
那么我們就通過給定配置參數的方式修改了服務器的運行端口號,當然也可以在配置文件中配置。
如果用戶為某些屬性設置了新值,那么服務器就使用用戶指定的值來更新相應的屬性,如果沒有設置新值,那么就是用上一步初始化的默認值。
載入配置后,進入下一步,初始化服務器數據結構。
在第一步的時候,服務器只創建了命令表一個數據結構,除了命令表之外,服務器還包括其他數據結構,比如server.clients鏈表,server.db數組等。
除了初始化這些數據結構之外,還需要做一些重要的設置操作,包括:
為服務器設置進程信號處理器。
創建共享對象,比如包含"OK","ERR"回復的字符串對象。
打開服務器的監聽端口,并未監聽套接字關聯連接應答處理器,等待服務器正式運行時接受客戶端的連接。
為serverCron函數創建時間事件,等待服務器正式運行時執行serverCron函數。
如果AOF持久化功能打開,那么打開現有的AOF文件,如果不存在,那么創建一個新的AOF文件,并未AOF寫入做好準備。
初始化服務器的后臺I/O模塊,為將來的I/O操作做好準備。
這步操作完成之后,進入下一步,還原數據庫狀態。
在完成率server變量的初始化之后,服務器還需要載入RDB文件或者AOF文件,并根據文件記錄的內容來還原服務器的數據庫狀態。RDB文件的優先級沒有AOF文件高!有AOF文件的話RDB文件就不會被使用了。
如果啟用了AOF持久化功能,那么服務器使用AOF文件來還原數據庫狀態。
如果沒有啟用AOF持久化功能,那么使用RDB文件來還原數據庫狀態。
數據庫狀態還原之后,開始執行最后一步,執行事件循環。
這是初始化的最后一步,服務器開始執行服務器的事件循環(loop),這個時候服務器的初始化就完成了,可以開始接受客戶端的連接請求,并處理客戶端發來的命令請求了。
感謝各位的閱讀,以上就是“什么是Redis事件和服務器”的內容了,經過本文的學習后,相信大家對什么是Redis事件和服務器這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。