天天看點

Redis:單線程模型效率為什麼這麼高,6.0為啥開始引入多線程Redis6.0之前是單線程模型Redis 6.0 開始引入多線程總結

 目錄 Redis6.0之前是單線程模型 檔案事件處理器 檔案事件 用戶端與 Redis 的一次通信過程: 為啥 Redis 單線程模型也能效率這麼高? Redis單線程問題 Redis 6.0 開始引入多線程 開啟IO多線程 總結

首先我們要明确一個共識,我們通常所說的Redis單線程是指擷取 (socket 讀)、解析、執行、内容傳回 (socket 寫) 等都由一個順序串行的主線程處理,這個主線程就是我們平時說的"單線程",而其他的清理髒資料、無用連接配接的釋放、LRU淘汰政策等等也是有其他線程在處理的,是以其實在Redis6之前的Redis本質上也是多線程的。

為什麼這些操作要放在同一個主線程中,官方給出的解釋:

傳送門
  • 通常瓶頸不在 CPU,而是在記憶體和網絡IO;
  • 多線程會帶來線程不安全的情況;
  • 多線程可能存線上程切換、甚至加鎖解鎖、死鎖造成的性能損耗;
  • 單線程降低了Redis内部實作複雜度;
  • hash的惰性rehash,lpush等線程不安全的指令可以無鎖執行;

Redis基于reactor模式開發了網絡事件處理器,這個處理器叫做檔案事件處理器,file event handler。

Redis 内部使用檔案事件處理器

file event handler

,這個檔案事件處理器是單線程的,是以 Redis 才叫做單線程的模型。它采用 IO 多路複用機制同時監聽多個 socket,将産生事件的 socket 壓入記憶體隊列中,事件分派器根據 socket 上的事件類型來選擇對應的事件處理器進行處理。

如果被監聽的socket準備好執行accept、read、write、close等操作的時候,跟操作對應的檔案事件就會産生,這個時候檔案事件處理器就會調用之前關聯好的事件處理器來處理這個事件。

檔案事件處理器是單線程模式運作的,但是通過IO多路複用機制監聽多個socket,可以實作高性能的網絡通信模型,又可以跟内部其他單線程的子產品進行對接,保證了redis内部的線程模型的簡單性。

檔案事件處理器的結構包含 4 個部分:

  • 多個 socket
  • IO 多路複用程式
  • 檔案事件分派器
  • 事件處理器(連接配接應答處理器、指令請求處理器、指令回複處理器)

多個 socket 可能會并發産生不同的操作,每個操作對應不同的檔案事件,但是 IO 多路複用程式會監聽多個 socket,會将産生事件的 socket 放入隊列中排隊,事件分派器每次從隊列中取出一個 socket,根據 socket 的事件類型交給對應的事件處理器進行處理。

當socket變得可讀時(比如用戶端對redis執行write操作,或者close操作),或者有新的可以應答的sccket出現時(用戶端對redis執行connect操作),socket就會産生一個AE_READABLE事件。

當socket變得可寫的時候(用戶端對redis執行read操作),socket會産生一個AE_WRITABLE事件。

IO多路複用程式可以同時監聽AE_REABLE和AE_WRITABLE兩種事件,要是一個socket同時産生了AE_READABLE和AE_WRITABLE兩種事件,那麼檔案事件分派器優先處理AE_REABLE事件,然後才是AE_WRITABLE事件。

如果是用戶端要連接配接redis,那麼會為socket關聯連接配接應答處理器

如果是用戶端要寫資料到redis,那麼會為socket關聯指令請求處理器

如果是用戶端要從redis讀資料,那麼會為socket關聯指令回複處理器

Redis:單線程模型效率為什麼這麼高,6.0為啥開始引入多線程Redis6.0之前是單線程模型Redis 6.0 開始引入多線程總結

用戶端與 Redis 的一次通信過程

Redis:單線程模型效率為什麼這麼高,6.0為啥開始引入多線程Redis6.0之前是單線程模型Redis 6.0 開始引入多線程總結

要明白,通信是通過 socket 來完成的,不懂的同學可以先去看一看 socket 網絡程式設計。

首先,Redis 服務端程序初始化的時候,會将 server socket 的

AE_READABLE

事件與連接配接應答處理器關聯。

用戶端 socket01 向 Redis 程序的 server socket 請求建立連接配接,此時 server socket 會産生一個

AE_READABLE

事件,IO 多路複用程式監聽到 server socket 産生的事件後,将該 socket 壓入隊列中。檔案事件分派器從隊列中擷取 socket,交給連接配接應答處理器。連接配接應答處理器會建立一個能與用戶端通信的 socket01,并将該 socket01 的

AE_READABLE

事件與指令請求處理器關聯。

假設此時用戶端發送了一個

set key value

請求,此時 Redis 中的 socket01 會産生

AE_READABLE

事件,IO 多路複用程式将 socket01 壓入隊列,此時事件分派器從隊列中擷取到 socket01 産生的

AE_READABLE

事件,由于前面 socket01 的

AE_READABLE

事件已經與指令請求處理器關聯,是以事件分派器将事件交給指令請求處理器來處理。指令請求處理器讀取 socket01 的

key value

并在自己記憶體中完成

key value

的設定。操作完成後,它會将 socket01 的

AE_WRITABLE

事件與指令回複處理器關聯。

如果此時用戶端準備好接收傳回結果了,那麼 Redis 中的 socket01 會産生一個

AE_WRITABLE

事件,同樣壓入隊列中,事件分派器找到相關聯的指令回複處理器,由指令回複處理器對 socket01 輸入本次操作的一個結果,比如

ok

,之後解除 socket01 的

AE_WRITABLE

事件與指令回複處理器的關聯。

這樣便完成了一次通信。

Redis 抽象了一套 AE 事件模型,将 IO 事件和時間事件融入一起,同時借助多路複用機制(linux上用epoll) 的回調特性,使得 IO 讀寫都是非阻塞的,實作高性能的網絡處理能力。加上 Redis 基于記憶體的資料處理,這就是 “單線程,但卻高性能” 的核心原因。

  1. 純記憶體操作。
  2. 核心是基于非阻塞的 IO 多路複用機制。
  3. C 語言實作,一般來說,C 語言實作的程式“距離”作業系統更近,執行速度相對會更快。
  4. 單線程反而避免了多線程的頻繁上下文切換問題,預防了多線程可能産生的競争問題。

 IO 資料的讀寫依然是阻塞的,這也是 Redis 目前的主要性能瓶頸之一,特别是在資料吞吐量特别大的時候,

  1. 單線程無法利用多CPU
  2. 串行操作,某個操作“出問題”會“阻塞”後續操作

注意! Redis 6.0 之後的版本抛棄了單線程模型這一設計,原本使用單線程運作的 Redis 也開始選擇性地使用多線程模型。

前面還在強調 Redis 單線程模型的高效性,現在為什麼又要引入多線程?這其實說明 Redis 在有些方面,單線程已經不具有優勢了。因為讀寫網絡的 Read/Write 系統調用在 Redis 執行期間占用了大部分 CPU 時間,如果把網絡讀寫做成多線程的方式對性能會有很大提升。

Redis 的多線程部分隻是用來處理網絡資料的讀寫和協定解析,執行指令仍然是單線程。 之是以這麼設計是不想 Redis 因為多線程而變得複雜,需要去控制 key、lua、事務、LPUSH/LPOP 等等的并發問題。

預設情況下,Redis多線程是禁用的,我們可以在redis.conf配置檔案選擇開啟:

#開啟IO多線程
io-threads-do-reads yes
 
#配置線程數量,如果設為1就是主線程模式。
io-threads 4      
Redis:單線程模型效率為什麼這麼高,6.0為啥開始引入多線程Redis6.0之前是單線程模型Redis 6.0 開始引入多線程總結
官方建議:至少4核的機器才開啟IO多線程,并且除非真的遇到了性能瓶頸,否則不建議開啟此配置 ,且配置的線程數少于機器總線程數,如果有4核建議開啟2,3個線程,如果有8核建議開6線程。 線程并不是越多越好,多于8個線程意義不大。

Redis 選擇使用單線程模型處理用戶端的請求主要還是因為 CPU 不是 Redis 伺服器的瓶頸,是以使用多線程模型帶來的性能提升并不能抵消它帶來的開發成本和維護成本,系統的性能瓶頸也主要在網絡 I/O 操作上;而 Redis 引入多線程操作也是出于性能上的考慮,對于一些大鍵值對的删除操作,通過多線程非阻塞地釋放記憶體空間也能減少對 Redis 主線程阻塞的時間,提高執行的效率。

  1. Redis的多路複用技術,支援epoll、kqueue、selector
  2. 5.0版本及以前,處理用戶端請求的線程隻有一個,串行處理
  3. 6.0版本引入了worker Thread,隻處理網絡IO讀取和寫入,核心IO負責串行處理用戶端指令

參考連結:

advanced-java/redis-single-thread-model.md at main · doocs/advanced-java · GitHub Redis系列(十六)、Redis6新特性之IO多線程_王義凱 的部落格-CSDN部落格

繼續閱讀