溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

什么是Redis事件和服務(wù)器

發(fā)布時間:2021-06-23 13:32:38 來源:億速云 閱讀:156 作者:chen 欄目:大數(shù)據(jù)

這篇文章主要講解了“什么是Redis事件和服務(wù)器”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“什么是Redis事件和服務(wù)器”吧!

Redis事件和服務(wù)器

事件

Redis是個單線程的,但是速度非常快,其主要原因是因為它是基于事件的,是一個事件驅(qū)動程序,了解NIO的應(yīng)該都知道這種方式。

Redis服務(wù)器需要處理兩類事件。

  • 文件事件(file event): Redis服務(wù)器通過套接字與客戶端進行連接,而**文件事件就是服務(wù)器對套接字操作的抽象。**服務(wù)器與客戶端的通信會產(chǎn)生相應(yīng)的文件事件,而服務(wù)器通過監(jiān)聽并處理這些事件來完成一系列網(wǎng)絡(luò)通信操作。

  • 時間事件(file event): Redis服務(wù)器中的一些操作(比如serverCron函數(shù))需要在給定的時間點執(zhí)行,而時間事件就是服務(wù)器對這類定時操作的抽象。

文件事件

Redis基于Reactor模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器,則個處理器被稱為文件時間處理器(file event handler):

  • 文件事件處理器采用I/O多路復(fù)用(multiplexing)程序來同時監(jiān)聽多個套接字,并根據(jù)套接字目前嶄新的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器。

  • 當(dāng)被監(jiān)聽的套接字準(zhǔn)備好連接應(yīng)答(accept),讀?。╮ead),寫入(write),關(guān)閉(close)等操作時,與操作對應(yīng)的文件事件就會產(chǎn)生,這是文件時就處理器就會調(diào)用套接字之前關(guān)聯(lián)號的事件處理器來處理這些事件。

構(gòu)成

文件時間處理器由四個部分組成,分別是套接字。I/O多路復(fù)用程序,文件事件分發(fā)器(dispatcher)和事件處理器。

什么是Redis事件和服務(wù)器

文件事件是對套接字操作的抽象,每當(dāng)一個套接字準(zhǔn)備好執(zhí)行連接應(yīng)答,寫入,讀取,關(guān)閉等操作時,就會產(chǎn)生一個文件時間。因為一個服務(wù)器會連接多個套接字,所有文件事件可能并發(fā)出現(xiàn)。

I/O多路復(fù)用程序負責(zé)監(jiān)聽多個套接字,并向文件事件分發(fā)器傳送那些產(chǎn)生了時間的套接字。I/O多路復(fù)用程序總是將所有產(chǎn)生時間的套接字放到一個隊列里面,然后通過這個隊列,以有序,同步,每次一個套接字的方式向文件時間分發(fā)器傳送套接字。

什么是Redis事件和服務(wù)器

文件事件分發(fā)器接收I/O多路復(fù)用程序傳來的套接字,并根據(jù)套接字產(chǎn)生的時間類型,調(diào)用相應(yīng)的事件處理器。

服務(wù)器會為執(zhí)行不同的任務(wù)的套接字關(guān)聯(lián)不同的事件處理器,這些處理器是一個個函數(shù),它們定義某個時間發(fā)生時,服務(wù)器應(yīng)該執(zhí)行的操作。

Tips: Redis的I/O多路復(fù)用程序的所有功能都是通過包裝常見的select,epoll,evport和kqueue這些I/O多路復(fù)用函數(shù)庫來實現(xiàn)的,并且為每個多路復(fù)用函數(shù)庫都實現(xiàn)了相同的API,因此底層是可以互換的。在編譯的時候,會自動選擇系統(tǒng)中性能最高的I/O多路復(fù)用函數(shù)庫來作為底層實現(xiàn)。

客戶端發(fā)起請求示例:

什么是Redis事件和服務(wù)器

時間事件

Redis的時間事件也有兩類:

  • 定時事件: 讓一段程序在指定的時間之后執(zhí)行一次。

  • 周期性事件: 讓一段程序每隔指定時間就執(zhí)行一次。

所有的時間事件都放在一個無序鏈表中,每當(dāng)事件執(zhí)行器運行時,它就遍歷整個鏈表,查找所有已到達的時間事件,并調(diào)用相應(yīng)的事件處理器。(這里的無序是指事件到達時間無序)

serverCron函數(shù)

serverCron函數(shù)負責(zé)定期對Redis的資源和狀態(tài)進行檢查和調(diào)整,主要工作包括:

  • 更新服務(wù)器的各類統(tǒng)計信息,比如時間,內(nèi)存占用,數(shù)據(jù)庫占用情況等。

  • 清理數(shù)據(jù)庫中的過期鍵值對。

  • 關(guān)閉和清理連接失效的客戶端。

  • 嘗試進行AOF或RDB持久化操作。

  • 如果是master,那么對從服務(wù)進行定期同步。

  • 如果處于cluster,對集群進行定期同步和連接測試。

該函數(shù)默認執(zhí)行時間是每秒10次,可以通過調(diào)整配置hz的值來改變,這個值代表的是每秒執(zhí)行的次數(shù)哦!

hz 10

更新服務(wù)器時間緩存

Redis服務(wù)器中有不少功能要獲取系統(tǒng)當(dāng)前時間,而每次獲取都需要執(zhí)行一次系統(tǒng)調(diào)用,為了減少系統(tǒng)調(diào)用次數(shù),服務(wù)器狀態(tài)中額unixtime屬性和mstime被用作當(dāng)前時間緩存。

struct redisServer {
    // ...
    // 保存秒級進度的系統(tǒng)當(dāng)前UNIX時間戳
    time_t unixtime;
    // 保存毫秒級進度的系統(tǒng)當(dāng)前UNIX時間戳
    long long mstime;
    // ...
};

因為函數(shù)每秒運行10次,100毫秒一次,所以這兩個屬性的精確度不高。

  • 服務(wù)器只會在打印日志,更細那服務(wù)器的LRU時鐘,決定好似否執(zhí)行持久化任務(wù)、計算服務(wù)器上線時間這類對時間精確度要求不高的功能上。

  • 對于為鍵設(shè)置過期時間,添加慢查詢?nèi)罩具@種需要高精確度時間的功能來說,服務(wù)器還是會再次執(zhí)行系統(tǒng)調(diào)用,從而獲得最準(zhǔn)確的系統(tǒng)當(dāng)前時間。

更新LRU時鐘

服務(wù)器狀態(tài)中的lruclock屬性保存了服務(wù)器的LRU始終,這個屬性也是服務(wù)器時間緩存的一種,用來計算對象空轉(zhuǎn)時長。

struct redisServer {
    // ...
    // 默認每秒更新一次
    unsigned lruclock:22;
    //...
}

每個redis對象有l(wèi)ru屬性,這個屬性保存了對象最后一次被命令訪問的時間。

空轉(zhuǎn)時間=lruclock - lru.

命令:

OBJECT IDLETIME key: 查看對象空轉(zhuǎn)時長
INFO server: 可以輸出server中l(wèi)ruclock的值

更新服務(wù)器每秒執(zhí)行命令次數(shù)

redis> INFO stats
# Stats
...
instantaneous_ops_per_sec: 558
...

以上命令結(jié)果顯示,在最近一秒之內(nèi),服務(wù)器大概處理了558個命令。

更新服務(wù)器內(nèi)存峰值記錄

服務(wù)器stat_peak_memory屬性記錄了服務(wù)器內(nèi)存峰值大小。

redis> INFO memory
# Memory
...
used_memory_peak:6180016
used_memory_peak_human:5.89M
...

以上命令查看內(nèi)存峰值大小。

處理SIGTERM信號

啟動服務(wù)器時,Redis會為進程的SIGTERM信號關(guān)聯(lián)處理器(一個函數(shù)),這個信號處理器負責(zé)在服務(wù)器接收到SIGTERM信號時,打開服務(wù)器狀態(tài)的shutdown_asap標(biāo)識。

每次serverCron函數(shù)運行時,程序都會對服務(wù)器狀態(tài)的shutdown_asap屬性進行檢查,如果值為1,則服務(wù)器會先進行RDB持久化操作,然后關(guān)閉服務(wù)器。

管理客戶端資源

主要是檢查客戶端:

  • 如果客戶端與服務(wù)器之間連接已經(jīng)超時,則釋放這個客戶端。

  • 如果客戶端在上一次執(zhí)行命令請求之后,輸入緩沖區(qū)的大小超過了一定長度,那么程序或釋放客戶端當(dāng)前的輸入緩沖區(qū),并重新創(chuàng)建一個默認大小的輸入緩沖區(qū),從而防止客戶端你的輸入緩沖區(qū)耗費過多的內(nèi)存。

管理數(shù)據(jù)庫資源

這個部分就主要是檢查服務(wù)器中的一部分?jǐn)?shù)據(jù)庫,刪除其中的過期鍵,并在有需要時,對字典(hash表)進行收縮操作。

執(zhí)行被延遲的BGREWRTEAOF

在服務(wù)器執(zhí)行BGSAVE命令期間,如果客戶端向服務(wù)器發(fā)來BGREWRITEAOF命令,那么服務(wù)器會將BGREWRITEAOF命令的執(zhí)行時間延遲到BGSAVE命令執(zhí)行完畢之后。

服務(wù)器的aof_rewrite_scheduled屬性記錄了是否延遲了BGREWRITEAOF命令。

struct redisServer {
    // ...
    // 值為1表示有BGREWRITEAOF命令被延遲了
    int aof_rewrite_scheduled;
    // ...
}

serverCron命令運行時都會檢查BGSAVE和BGREWRITEAOF命令是否在執(zhí)行,如果都沒有執(zhí)行,那么檢查服務(wù)器的aof_rewrite_scheduled屬性,如果值為1,那么就執(zhí)行延遲的BGREWRITEAOF命令.

檢查持久化操作的運行狀態(tài)

服務(wù)器狀態(tài)使用rdb_child_pid和aof_child_pid屬性記錄執(zhí)行BGSAVE命令和BGREWRITEAOF命令的子進程ID,這兩個屬性也可以用于檢查對應(yīng)命令是否正在執(zhí)行:

struct redisServer {
    // ...
    // 記錄執(zhí)行BGSAVE命令的子進程ID
    // 如果沒有執(zhí)行,值為-1
    pid_t rdb_child_pid;
    // 記錄執(zhí)行BGREWRITEAOF命令的子進程ID
    // 如果沒有執(zhí)行,值為-1
    pid_t aof_child_pid;
    // ...
}

serverCron函數(shù)每次執(zhí)行時,程序都會檢查這兩個屬性的值,只要其中一個屬性不為-1,程序就會檢查子進程是否有信號發(fā)來服務(wù)器進程(調(diào)用其他函數(shù))。

  • 如果有信號到達,那么表示新的RDB文件已經(jīng)生成完畢或者AOF文件已經(jīng)重寫完畢,服務(wù)器需要進行后續(xù)操作,比如新的AOF替換舊的AOF文件。

  • 如果沒有信號到達,表示持久化操作未完成,程序不做動作。

如果這兩個屬性的值都為-1,那么做三個檢查:

  • 檢查是否有BGREWRITEAOF命令被延遲,如果有, 開始執(zhí)行命令。

  • 檢查自動保存條件是否滿足,如果滿足,且服務(wù)器當(dāng)前沒有執(zhí)行其他持久化操作,那么開始一次新的BGSAVE操作(因為上個檢查可能會引發(fā)一次新的BGREWRITEAOF,所有這次檢查中,程序會再次確認是否已經(jīng)在執(zhí)行持久化操作了)。

  • 檢查服務(wù)器設(shè)置的AOF重寫條件是否滿足,如果條件滿足,并且服務(wù)器沒有執(zhí)行其他持久化操作,那么開始一次新的BGREWRITEAOF操作(也會挨次確認,因為上兩個檢查都可能會引發(fā)新的持久化操作)。

圖解:

什么是Redis事件和服務(wù)器

將AOF緩沖區(qū)的內(nèi)容寫入AOF文件

如果服務(wù)器開啟了AOF持久化功能,并且AOF緩沖區(qū)里面還有待寫入的數(shù)據(jù),那么serverCron函數(shù)會調(diào)用相應(yīng)的程序,將AOF緩沖區(qū)中的內(nèi)容寫入到AOF文件。

關(guān)閉異步客戶端

這一步,服務(wù)器會關(guān)閉輸出緩沖區(qū)大小超出限制的客戶端。

客戶端的輸入緩沖區(qū)用于把醋你客戶端發(fā)送的命令請求:

typedef struct redisClient {
    // ...
    sds querybuf;
    // ...
}

這個緩沖區(qū)中保存的是協(xié)議的值。

set mKey mValue
querybuf的值為
*3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n

輸入緩沖區(qū)的大小會根據(jù)內(nèi)容動態(tài)的縮小或者擴大,但它的值不能超過1G,否則服務(wù)器將關(guān)閉這個客戶端。

增加cronloops計數(shù)器的值

服務(wù)器狀態(tài)的cronloops屬性記錄了serverCron函數(shù)執(zhí)行的次數(shù)

struct redisServer {
    // ...
    // serverCron函數(shù)沒執(zhí)行一次,值就加1
    int cronloops;
    // ...
}

這個屬性目前在服務(wù)器中的唯一作用,就是在復(fù)制模塊中實現(xiàn)沒執(zhí)行N次就執(zhí)行一次指定代碼的功能。

if (cronloops % n == 0) {
    // 指定代碼,這個指定的代碼應(yīng)該是服務(wù)器處理的一些事了,每隔多少次就處理一次
}

服務(wù)器

命令請求過程

命令請求的時候,在客戶端輸入命令的時候,客戶端會將這個命令轉(zhuǎn)換成協(xié)議,然后將協(xié)議內(nèi)容發(fā)送給服務(wù)器。

協(xié)議格式

*<參數(shù)數(shù)量> CR LF
$<參數(shù) 1 的字節(jié)數(shù)量> CR LF
<參數(shù) 1 的數(shù)據(jù)> CR LF
...
$<參數(shù) N 的字節(jié)數(shù)量> CR LF
<參數(shù) N 的數(shù)據(jù)> CR LF

注意:CRLF就是換行 \r\n

socket發(fā)送協(xié)議

我們先來試試:

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來發(fā)送協(xié)議給服務(wù)器。

public static void main(String[] args) throws Exception {
	Socket socket = new Socket("192.168.2.11", 16379);
	//獲取輸出流,向socket寫數(shù)據(jù)
	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();
	//關(guān)閉輸出流
	socket.shutdownOutput();

	//讀取服務(wù)器返回的socket的數(shù)據(jù)
	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("客戶端接收服務(wù)端發(fā)送信息:" + info);
	}

	//關(guān)閉相對應(yīng)的資源
	bufferedReader.close();
	inputStream.close();
	printWriter.close();
	outputStream.close();
	socket.close();
}

執(zhí)行結(jié)果:

客戶端接收服務(wù)端發(fā)送信息:+OK

Process finished with exit code 0

上服務(wù)器檢查是否有值:

redis> get mKey
"mValue"

服務(wù)器初始化過程

初始化服務(wù)器狀態(tài)

第一步就是創(chuàng)建一個strct redisServer類型的實例變量作為服務(wù)器狀態(tài),并為結(jié)構(gòu)中的各個屬性設(shè)置默認值。主要包括以下部分:

  • 設(shè)置服務(wù)器的運行ID。

  • 設(shè)置服務(wù)器的默認運行頻率。

  • 設(shè)置服務(wù)器的默認配置文件路徑。

  • 設(shè)置服務(wù)器的運行架構(gòu)。

  • 設(shè)置服務(wù)器的默認端口號。

  • 設(shè)置服務(wù)器的默認RDB持久化條件和AOF持久化條件。

  • 初始化服務(wù)器的LRU時鐘。

  • 創(chuàng)建命令表。

當(dāng)初始化之后,就進入下一步,載入配置項。

載入配置項

在啟動服務(wù)器時,用戶可以通過給定配置參數(shù)或者指定配置文件來修改服務(wù)器的默認配置,比如

redis-server --port 16379

那么我們就通過給定配置參數(shù)的方式修改了服務(wù)器的運行端口號,當(dāng)然也可以在配置文件中配置。

如果用戶為某些屬性設(shè)置了新值,那么服務(wù)器就使用用戶指定的值來更新相應(yīng)的屬性,如果沒有設(shè)置新值,那么就是用上一步初始化的默認值。

載入配置后,進入下一步,初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu)。

初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu)

在第一步的時候,服務(wù)器只創(chuàng)建了命令表一個數(shù)據(jù)結(jié)構(gòu),除了命令表之外,服務(wù)器還包括其他數(shù)據(jù)結(jié)構(gòu),比如server.clients鏈表,server.db數(shù)組等。

除了初始化這些數(shù)據(jù)結(jié)構(gòu)之外,還需要做一些重要的設(shè)置操作,包括:

  • 為服務(wù)器設(shè)置進程信號處理器。

  • 創(chuàng)建共享對象,比如包含"OK","ERR"回復(fù)的字符串對象。

  • 打開服務(wù)器的監(jiān)聽端口,并未監(jiān)聽套接字關(guān)聯(lián)連接應(yīng)答處理器,等待服務(wù)器正式運行時接受客戶端的連接。

  • 為serverCron函數(shù)創(chuàng)建時間事件,等待服務(wù)器正式運行時執(zhí)行serverCron函數(shù)。

  • 如果AOF持久化功能打開,那么打開現(xiàn)有的AOF文件,如果不存在,那么創(chuàng)建一個新的AOF文件,并未AOF寫入做好準(zhǔn)備。

  • 初始化服務(wù)器的后臺I/O模塊,為將來的I/O操作做好準(zhǔn)備。

這步操作完成之后,進入下一步,還原數(shù)據(jù)庫狀態(tài)。

還原數(shù)據(jù)庫狀態(tài)

在完成率server變量的初始化之后,服務(wù)器還需要載入RDB文件或者AOF文件,并根據(jù)文件記錄的內(nèi)容來還原服務(wù)器的數(shù)據(jù)庫狀態(tài)。RDB文件的優(yōu)先級沒有AOF文件高!有AOF文件的話RDB文件就不會被使用了。

  • 如果啟用了AOF持久化功能,那么服務(wù)器使用AOF文件來還原數(shù)據(jù)庫狀態(tài)。

  • 如果沒有啟用AOF持久化功能,那么使用RDB文件來還原數(shù)據(jù)庫狀態(tài)。

數(shù)據(jù)庫狀態(tài)還原之后,開始執(zhí)行最后一步,執(zhí)行事件循環(huán)。

執(zhí)行事件循環(huán)

這是初始化的最后一步,服務(wù)器開始執(zhí)行服務(wù)器的事件循環(huán)(loop),這個時候服務(wù)器的初始化就完成了,可以開始接受客戶端的連接請求,并處理客戶端發(fā)來的命令請求了。

感謝各位的閱讀,以上就是“什么是Redis事件和服務(wù)器”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對什么是Redis事件和服務(wù)器這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI