Redis內部阻塞式操作有哪些?


Redis實例在運行的時候,要和許多對象進行交互,這些不同的交互對象會有不同的操作。下面我們來看看,這些不同的交互對象以及相應的主要操作有哪些。

  • 客戶端:鍵值對的增刪改查操作。

  • 磁盤:生成RDB快照、記錄AOF日志、AOF日志重寫。

  • 主從節點:主庫生成、傳輸RDB文件,從庫接受RDB文件、清空數據庫、加載RDB文件。

    下面我們來分析一下哪些操作會引起主線程阻塞。  

1.和客戶端交互時的阻塞點。

      鍵值對的增刪改查操作是Redis和客戶端交互的主要部分,也是Redis主線程執行的主要任務。所以復雜度高的增刪改查操作肯定會阻塞Redis。在Redis中復雜度高的操作都是對集合的操作,通常時間復雜度為O(N)。所以在使用的過程中需要注意起來,例如對集合的全量查詢操作HGETALL以及對集合的聚合統計操作,例如求交、並和差集。所以集合全量查詢和聚合操作是Redis的一個阻塞點。

      除此之外,對集合刪除操作也會有潛在的阻塞風險。因為刪除操作的本質是要釋放鍵值對占用的內存空間。釋放內存只是第一步,為了更加高效地管理內存空間,在應用程序釋放內存時,操作系統需要把釋放掉的內存塊插入一個空閑內存塊的鏈表,以便后續進行管理和再分配。這個過程本身需要一定時間,而且會阻塞當前釋放內存的應用程序,所以,如果一下子釋放了大量內存,空閑內存塊鏈表操作時間就會增加,相應地就會造成 Redis 主線程的阻塞。所以刪除bigkey也是Redis的一個阻塞點。

     因為清空數據庫涉及到刪除和釋放所有的鍵值對。所以清空數據庫也是Redis的一個阻塞點。

2.和磁盤交互時的阻塞點。

    Redis是采用后台子進程的方式生成RDB快照文件,以及執行AOF日志重寫操作。所以這兩個操作由子進程負責執行,因此不會成為Redis的阻塞點。

但是Redis直接記錄AOF日志時,會根據不同的寫回策略對數據做落盤保存。

如果有大量的寫操作需要記錄在AOF日志中,並寫回策略設置成同步寫回的話,就會阻塞主線程了。所以AOF日志同步寫也是Redis的一個阻塞點。

3.主從節點交互時的阻塞點

    在主從集群中,主庫需要生成RDB文件,並傳輸給從庫。主庫在復制的過程中,創建和傳輸 RDB 文件都是由后台子進程來完成的,不會阻塞主線程的執行。但是,對於從庫來說,它在接收了RDB 文件后,需要使用 FLUSHDB 命令清空當前數據庫,然后需要把 RDB 文件加載到內存,這個過程的快慢和 RDB 文件的大小密切相關,RDB 文件越大,加載過程越慢,所以,加載 RDB 文件就成為了 Redis 又一個阻塞點。

     所以總結一下,Redis中阻塞主線程的操作主要有以下五種。

  • 集合全量查詢和聚合操作。

  • bigkey 刪除。

  • 清空數據庫。

  • AOF 日志同步寫。

  • 從庫加載 RDB 文件。

     那有什么方式可以避免阻塞式操作呢?Redis提供了異步線程機制。為了避免一些阻塞主線程的操作,Redis會啟動一些子線程,然后把一些任務交給這些子線程去在后台完成,而不再由主線程來執行這些任務。下面我們來看一下哪些阻塞主線程的操作可以采用異步執行呢?

       如果一個操作能被異步執行,就意味着,它並不是Redis主線程的關鍵路徑上的操作。所謂的關鍵路徑是指客戶端把請求發送給Redis后等着Redis返回數據結果的操作。如下圖所示。

 

 

 

     主線程接收到操作1后,因為操作1並不用給客戶端返回具體的數據,所以,主線程可以把它交給后台子線程來完成,同時只要給客戶端返回一個“OK”結果就行。在子線程執行操作1的時候,客戶端又向Redis實例發送了操作2,而此時,客戶端是需要使用操作2返回的數據結果的,如果操作2不返回結果,那么,客戶端將一直處於等待狀態。在這個例子中,操作 1 就不算關鍵路徑上的操作,因為它不用給客戶端返回具體數據,所以可以由后台子線程異步執行。而操作 2 需要把結果返回給客戶端,它就是關鍵路徑上的操作,所以主線程必須立即把這個操作執行完。

       對於 Redis 來說,讀操作是典型的關鍵路徑操作,因為客戶端發送了讀操作之后,就會等待讀取的數據返回。而Redis 的集合全量查詢和聚合操作都涉及到了讀操作,所以,它們是不能進行異步操作了。

       我們再來看看刪除操作。刪除操作並不需要給客戶端返回具體的數據結果,所以不是關鍵路徑操作。所以“bigkey 刪除”和“清空數據庫”,都是對數據做刪除,所以我們可以用后台子線程來異步執行刪除操作。

       對於“AOF 日志同步寫”來說,為了保證數據可靠性,Redis 實例需要保證 AOF 日志中的操作記錄已經落盤,這個操作雖然需要實例等待,但它並不會返回具體的數據結果給實例。所以,我們也可以啟動一個子線程來執行 AOF 日志的同步寫,而不用讓主線程等待 AOF 日志的寫完成。

       最后,我們再來看下“從庫加載 RDB 文件”這個操作。從庫要想對客戶端提供數據存取服務,就必須把 RDB 文件加載完成。所以,這個操作也屬於關鍵路徑上的操作,我們必須讓從庫的主線程來執行。

         對於 Redis 的五大阻塞點來說,除了“集合全量查詢和聚合操作”和“從庫加載 RDB 文件”,其他三個阻塞點涉及的操作都不在關鍵路徑上,所以,我們可以使用 Redis 的異步子線程機制來實現 bigkey 刪除,清空數據庫,以及 AOF 日志同步寫。

4.異步的子線程機制

       Redis 主線程啟動后,會使用操作系統提供的 pthread_create 函數創建 3 個子線程,分別由它們負責 AOF 日志寫操作、鍵值對刪除以及文件關閉的異步執行。主線程通過一個鏈表形式的任務隊列和子線程進行交互。當收到鍵值對刪除和清空數據庫的操作時,主線程會把這個操作封裝成一個任務,放入到任務隊列中,然后給客戶端返回一個完成信息,表明刪除已經完成。但實際上,這個時候刪除還沒有執行,等到后台子線程從任務隊列中讀取任務后,才開始實際刪除鍵值對,並釋放相應的內存空間。因此,我們把這種異步刪除也稱為惰性刪除(lazy free)。此時,刪除或清空操作不會阻塞主線程,這就避免了對主線程的性能影響。和惰性刪除類似,當 AOF 日志配置成 everysec 選項后,主線程會把 AOF 寫日志操作封裝成一個任務,也放到任務隊列中。后台子線程讀取任務后,開始自行寫入 AOF 日志,這樣主線程就不用一直等待 AOF 日志寫完了。

 

 

 

 

     當你的集合中有大量的元素要刪除時,我建議你使用異步刪除命令 UNLINK 。而對於清空數據庫來說,可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 選項,這樣就可以讓后台子線程異步地清空數據庫。

FLUSHDB ASYNC
FLUSHALL AYSNC

更多硬核知識,請關注公眾號"程序員學長"。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM