1. 程式人生 > 其它 >【轉載】redis多執行緒,redis IO多路複用

【轉載】redis多執行緒,redis IO多路複用

轉自:https://www.cnblogs.com/jelly12345/p/15136912.html

1. Redis6.0之前的版本真的是單執行緒嗎?

Redis在處理客戶端的請求時,包括獲取 (socket 讀)、解析、執行、內容返回 (socket 寫) 等都由一個順序序列的主執行緒處理,這就是所謂的“單執行緒”。但如果嚴格來講從Redis4.0之後並不是單執行緒,除了主執行緒外,它也有後臺執行緒在處理一些較

為緩慢的操作,例如清理髒資料、無用連線的釋放、大 key 的刪除等等。其中執行命令階段,由於 Redis 是單執行緒來處理命令的,所有每一條到達服務端的命令不會立刻執行,所有的命令都會進入一個 Socket 佇列中,當 socket 可讀則交給單執行緒事件分發器逐個被執行。如下圖所示:

2. Redis6.0之前為什麼一直不使用多執行緒?

官方曾做過類似問題的回覆:使用Redis時,幾乎不存在CPU成為瓶頸的情況, Redis主要受限於記憶體和網路。例如在一個普通的Linux系統上,Redis通過使用pipelining每秒可以處理100萬個請求,所以如果應用程式主要使用O(N)或O(log(N))的命令,它幾乎不會佔用太多CPU。

使用了單執行緒後,可維護性高。多執行緒模型雖然在某些方面表現優異,但是它卻引入了程式執行順序的不確定性,帶來了併發讀寫的一系列問題,增加了系統複雜度、同時可能存線上程切換、甚至加鎖解鎖、死鎖造成的效能損耗。Redis通過AE事件模型以及IO多路複用等技術,處理效能非常高,因此沒有必要使用多執行緒。單執行緒機制使得 Redis 內部實現的複雜度大大降低,Hash 的惰性 Rehash、Lpush 等等 “執行緒不安全” 的命令都可以無鎖進行。

3.Redis6.0為什麼要引入多執行緒呢?

Redis將所有資料放在記憶體中,記憶體的響應時長大約為100納秒,對於小資料包,Redis伺服器可以處理80,000到100,000 QPS,這也是Redis處理的極限了,對於80%的公司來說,單執行緒的Redis已經足夠使用了。

但隨著越來越複雜的業務場景,有些公司動不動就上億的交易量,因此需要更大的QPS。常見的解決方案是在分散式架構中對資料進行分割槽並採用多個伺服器,但該方案有非常大的缺點,例如要管理的Redis伺服器太多,維護代價大;某些適用於單個Redis伺服器的命令不適用於資料分割槽;資料分割槽無法解決熱點讀/寫問題;資料偏斜,重新分配和放大/縮小變得更加複雜等等。

從Redis自身角度來說,因為讀寫網路的read/write系統呼叫佔用了Redis執行期間大部分CPU時間,瓶頸主要在於網路的 IO 消耗, 優化主要有兩個方向:

  • 提高網路 IO 效能,典型的實現比如使用 DPDK 來替代核心網路棧的方式

  • 使用多執行緒充分利用多核,典型的實現比如 Memcached。

協議棧優化的這種方式跟 Redis 關係不大,支援多執行緒是一種最有效最便捷的操作方式。所以總結起來,redis支援多執行緒主要就是兩個原因:

  • 可以充分利用伺服器 CPU 資源,目前主執行緒只能利用一個核

  • 多執行緒任務可以分攤 Redis 同步 IO 讀寫負荷

4.Redis6.0預設是否開啟了多執行緒?

Redis6.0的多執行緒預設是禁用的,只使用主執行緒。如需開啟需要修改redis.conf配置檔案:io-threads-do-reads yes

5.Redis6.0多執行緒開啟時,執行緒數如何設定?

開啟多執行緒後,還需要設定執行緒數,否則是不生效的。同樣修改redis.conf配置檔案。關於執行緒數的設定,官方有一個建議:4 核的機器建議設定為 2 或 3 個執行緒,8核的建議設定為 6 個執行緒,執行緒數一定要小於機器核數。執行緒數並不是越大越好,官方認為超過了 8 個基本就沒什麼意義了。

6.Redis6.0多執行緒的實現機制?

(1).流程如下:

  1. 主執行緒獲取 socket 放入等待列表

  2. 將 socket 分配給各個 IO 執行緒(並不會等列表滿)

  3. 主執行緒阻塞等待 IO 執行緒(多執行緒)讀取 socket 完畢

  4. 主執行緒執行命令 - 單執行緒(如果命令沒有接收完畢,會等 IO 下次繼續)

  5. 主執行緒阻塞等待 IO 執行緒(多執行緒)將資料回寫 socket 完畢(一次沒寫完,會等下次再寫)

  6. 解除繫結,清空等待佇列

(2).特點如下:

  • IO 執行緒要麼同時在讀 socket,要麼同時在寫,不會同時讀或寫

  • IO 執行緒只負責讀寫 socket 解析命令,不負責命令處理(主執行緒序列執行命令)

  • IO 執行緒數可自行配置

流程簡述如下:

  • 1、主執行緒負責接收建立連線請求,獲取 socket 放入全域性等待讀處理佇列

  • 2、主執行緒處理完讀事件之後,通過 RR(Round Robin) 將這些連線分配給這些 IO 執行緒

  • 3、主執行緒阻塞等待 IO 執行緒讀取 socket 完畢

  • 4、主執行緒通過單執行緒的方式執行請求命令,請求資料讀取並解析完成,但並不執行

  • 5、主執行緒阻塞等待 IO 執行緒將資料回寫 socket 完畢

  • 6、解除繫結,清空等待佇列

該設計有如下特點:

  • IO 執行緒要麼同時在讀 socket,要麼同時在寫,不會同時讀或寫

  • IO 執行緒只負責讀寫 socket 解析命令,不負責命令處理

7.開啟多執行緒後,是否會存線上程併發安全問題?

從上面的實現機制可以看出,Redis的多執行緒部分只是用來處理網路資料的讀寫和協議解析,執行命令仍然是單執行緒順序執行。所以我們不需要去考慮控制 key、lua、事務,LPUSH/LPOP 等等的併發及執行緒安全問題。

8.redis單執行緒模型(6.0之前)

Redis客戶端對服務端的每次呼叫都經歷了傳送命令,執行命令,返回結果三個過程。其中執行命令階段,由於Redis是單執行緒來處理命令的,所有每一條到達服務端的命令不會立刻執行,所有的命令都會進入一個佇列中,然後逐個被執行。並且多個客戶端傳送的命令的執行順序是不確定的。但是可以確定的是不會有兩條命令被同時執行,不會產生併發問題,這就是Redis的單執行緒基本模型。

PS:可以修改redis的最大連結數,預設為10000,如下圖,如果要修改的話,直接修改配置檔案重點maxclients即可。

8.1. 什麼是非阻塞IO?

非阻塞 IO 在 Socket 物件上提供了一個選項Non_Blocking ,當這個選項開啟時,讀寫方法不會阻塞,而是能讀多少讀多少,能寫多少寫多少。

能讀多少取決於核心為 Socket 分配的讀緩衝區的大小,能寫多少取決於核心為 Socket 分配的寫緩衝區的剩餘空間大小。讀方法和寫方法都會通過返回值來告知程式實際讀寫了多少位元組資料。

有了非阻塞 IO 意味著執行緒在讀寫 IO 時可以不必再阻塞了,讀寫可以瞬間完成然後執行緒可以繼續幹別的事了。

補充阻塞IO概念:

當我們呼叫 Scoket 的讀寫方法,預設它們是阻塞的。

read() 方法要傳遞進去一個引數 n,表示讀取這麼多位元組後再返回,如果沒有讀夠 n 位元組執行緒就會阻塞,直到新的資料到來或者連線關閉了, read 方法才可以返回,執行緒才能繼續處理。

write() 方法會首先把資料寫到系統核心為 Scoket 分配的寫緩衝區中,當寫快取區滿溢,即寫快取區中的資料還沒有寫入到磁碟,就有新的資料要寫道寫快取區時,write() 方法就會阻塞,直到寫快取區中有空閒空間。

8.2. 什麼是IO多路複用?

背景:

非阻塞 IO 有個問題,那就是單個執行緒要處理多個讀寫請求,處理某個客戶端的的讀資料的請求,結果讀了一部分就返回了,執行緒如何知道什麼時候才應該繼續讀資料。處理寫請求的時候,如果緩衝區滿了,寫不完,剩下的資料何時才應該繼續寫?在什麼時候處理什麼請求?redis 單執行緒處理多個IO請求時就用到了IO多路複用技術。

原理:

如下圖,redis 需要處理 3 個 IO 請求,同時把 3 個請求的結果返回給客戶端,所以總共需要處理 6 個 IO 事件,由於 redis 是單執行緒模型,同一時間只能處理一個 IO 事件,於是 redis 需要在合適的時間暫停對某個 IO 事件的處理,轉而去處理另一個 IO 事件,這樣 redis 就好比一個開關,當開關撥到哪個 IO 事件這個電路上,就處理哪個 IO 事件,其他 IO 事件就暫停處理了。這就是IO多路複用技術。

以上是大致的理解下 IO 多路複用技術,在系統底層,IO 多路複用有 3 種實現機制:select、poll、epoll。

 

8.3.什麼是檔案處理器?

  • Redis 基於 Reactor 模式開發了自己的網路事件處理器: 這個處理器被稱為檔案事件處理器(file event handler)

  • 檔案事件處理器使用 I/O 多路複用(multiplexing)程式來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。

  • 當被監聽的套接字準備好執行連線應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時,與操作相對應的檔案事件就會產生,這時檔案事件處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件。

  • 檔案事件處理器以單執行緒方式執行,但通過使用 I/O 多路複用程式來監聽多個套接字,檔案事件處理器既實現了高效能的網路通訊模型,又可以很好地與 redis 伺服器中其他同樣以單執行緒方式執行的模組進行對接, 這保持了 Redis 內部單執行緒設計的簡單性。

總結與思考

隨著網際網路的飛速發展,網際網路業務系統所要處理的線上流量越來越大,Redis 的單執行緒模式會導致系統消耗很多 CPU 時間在網路 I/O 上從而降低吞吐量,要提升 Redis 的效能有兩個方向:

  • 優化網路 I/O 模組

  • 提高機器記憶體讀寫的速度

後者依賴於硬體的發展,暫時無解。所以只能從前者下手,網路 I/O 的優化又可以分為兩個方向:

  • 零拷貝技術或者 DPDK 技術

  • 利用多核優勢

模型缺陷

Redis 的多執行緒網路模型實際上並不是一個標準的 Multi-Reactors/Master-Workers模型。

Redis 的多執行緒方案中,I/O 執行緒任務僅僅是通過 socket 讀取客戶端請求命令並解析,卻沒有真正去執行命令。

所有客戶端命令最後還需要回到主執行緒去執行,因此對多核的利用率並不算高,而且每次主執行緒都必須在分配完任務之後忙輪詢等待所有 I/O 執行緒完成任務之後才能繼續執行其他邏輯。

在我看來,Redis 目前的多執行緒方案更像是一個折中的選擇:既保持了原系統的相容性,又能利用多核提升 I/O 效能。

參考部落格:

https://www.cnblogs.com/mumage/p/12832766.html

https://ruby-china.org/topics/38957%EF%BC%89

https://www.cnblogs.com/yaopengfei/p/13946966.html

https://mp.weixin.qq.com/s/yxFTOq1Xki5dJnznZccz_A