Redis6.0多線程模型總結


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


免責聲明!

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



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