0. 背景
Redis作為一個基於內存的緩存系統,一直以高性能著稱,
在單線程處理情況下,讀速度可達到11萬次/s,寫速度達到8.1萬次/s。
Redis6.0之前為什么一直不使用多線程?
官方曾做過類似問題的回復:使用Redis時,幾乎不存在CPU成為瓶頸的情況, Redis主要受限於內存和網絡。
但是,單線程的設計也給Redis帶來一些問題:
- 只能使用CPU一個核
- 如果刪除的鍵過大(eg: Set類型中有上百萬個對象),會導致服務端阻塞好幾秒
- QPS難再提高
針對上面問題,Redis在4.0版本以及6.0版本分別引入了Lazy Free以及多線程IO,逐步向多線程過渡。
1. Redis單線程架構原理
Redis單線程是如何支持客戶端並發請求的呢?
Redis服務器是一個事件驅動程序,服務器需要處理以下兩類事件:
文件事件
Redis服務器通過套接字與客戶端(或者其他Redis服務器)進行連接。
文件事件就是服務器對套接字操作的抽象。
服務器與客戶端的通信會產生相應的文件事件,而服務器則通過監聽並處理這些事件來完成一系列網絡通信操作。
(eg: 連接accept,read,write,close等)
時間事件
Redis服務器中的一些操作(eg: serverCron函數)需要在給定的時間點執行。
時間事件就是服務器對這類定時操作的抽象。
(eg: 過期鍵清理,服務狀態統計等)
Redis將文件事件和時間事件進行抽象,時間輪詢器會監聽I/O事件表:
一旦有文件事件就緒,Redis就會優先處理文件事件,
接着處理時間事件。
在上述所有事件處理上,Redis都是以單線程形式處理,所以說Redis是單線程的。
處理過程見下圖
Redis基於Reactor模式開發了自己的I/O事件處理器,也就是文件事件處理器。
Redis在I/O事件處理上,采用了I/O多路復用技術,同時監聽多個套接字,
並為套接字關聯不同的事件處理函數,通過一個線程實現了多客戶端並發處理。
處理過程見下圖
上述的設計,在數據處理上避免了加鎖操作,既使得實現上足夠簡潔,也保證了其高性能。
當然,Redis單線程只是指其在事件處理上,實際上,Redis也並不是單線程的,比如生成RDB文件,就會fork一個子進程來實現。
2. Redis 4.0的Lazy Free機制
背景:
客戶端向Redis發送一條耗時較長的命令,比如刪除一個含有上百萬對象的Set鍵,或者執行flushdb,flushall操作,
Redis服務器需要回收大量的內存空間,導致服務器卡住好幾秒,對負載較高的緩存系統而言將會是個災難。
為了解決這個問題,在Redis 4.0版本引入了Lazy Free,將慢操作異步化,這也是在事件處理上向多線程邁進了一步。
將大鍵的刪除操作異步化,采用非阻塞刪除(對應命令UNLINK)。
大鍵的空間回收交由單獨線程實現,主線程只做關系解除,可以快速返回,繼續處理其他事件,避免服務器長時間阻塞。
意義:
Redis在4.0版本引入了Lazy Free,自此Redis有了一個**Lazy Free線程專門用於大鍵的回收**。
同時,也去掉了聚合類型的共享對象,這為多線程帶來可能。
這為Redis在6.0版本實現了多線程I/O打下了基礎。
3. Redis 6.0多線程實現機制
流程簡述如下:
1、主線程負責接收建立連接請求,獲取 socket 放入全局等待讀處理隊列
2、主線程處理完讀事件之后,通過 RR(Round Robin) 將這些連接分配給這些 IO 線程
3、主線程阻塞等待 IO 線程讀取 socket 完畢
4、主線程通過單線程的方式執行請求命令,請求數據讀取並解析完成,但並不執行
5、主線程阻塞等待 IO 線程將數據回寫 socket 完畢
6、解除綁定,清空等待隊列
見下圖
4. Redis6.0多線程的設置
Redis6.0的多線程默認是禁用的,只使用主線程。
如需開啟需要修改redis.conf配置文件:
io-threads-do-reads yes
開啟多線程后,還需要設置線程數,否則是不生效的。
同樣修改redis.conf配置文件:
io-threads 4