眾所周知,Redis是一個單執行緒架構的NoSQL資料庫,但是是單執行緒模型的Redis為什麼效能如此之高?這就是我們接下來要探究學習的內容。
1、Redis的單執行緒架構
1.1、Redis單執行緒簡介
首先要明白,Redis的單執行緒指的是執行命令時的單執行緒
。
Redis客戶端與服務端的模型可以簡化成下圖,每次客戶端呼叫都經歷了傳送命令、執行命令、返回結果三個過程。
我們說的單執行緒就是在第二步執行命令
,一條命令從從客戶端達到服務端不會立刻被執行,而是會進入一個佇列中等待,每次只會有一條指令被選中執行。
傳送命令、返回結果、命令排隊這些就不是那麼簡單了,例如Redis使用了I/O多路複用技術來解決I/O的問題。
1.2、Redis為什麼要使用單執行緒
這是官方的解釋:https://redis.io/topics/faq
官方FAQ表示,因為Redis是基於記憶體的操作,CPU成為Redis的瓶頸的情況很少見,Redis的瓶頸最有可能是記憶體的大小或者網路限制。
如果想要最大程度利用CPU,可以在一臺機器上啟動多個Redis例項。
值得一提的,網路上存在這樣的觀點:吐槽官方的解釋有些敷衍,其實就是歷史原因,開發者嫌多執行緒麻煩,後來這個CPU的利用問題就被拋給了使用者。
同時FAQ裡還提到了, Redis 4.0 之後開始變成多執行緒,除了主執行緒外,它也有後臺執行緒在處理一些較為緩慢的操作,例如清理髒資料、無用連線的釋放、大 Key 的刪除等等。
1.3、為什麼單執行緒還能這麼快
通常來講,單執行緒處理能力要比多執行緒差,那麼為什麼Redis使用單執行緒模型會達到每秒萬級別的處理能力呢?可以將其歸結為三點:
-
第一:純記憶體訪問,Redis將所有資料放在記憶體中,記憶體的響應時長大約為100納秒,這是Redis達到每秒萬級別訪問的最重要的基礎。
-
第二:非阻塞I/O,Redis使用epoll作為I/O多路複用技術的實現,再加上Redis自身的事件處理模型將epoll中的連線、讀寫、關閉都轉換為事件,不在網路I/O上浪費過多的時間
這裡再擴充套件一下I/O多路複用:
引用知乎上一個高讚的回答來解釋什麼是I/O多路複用。假設你是一個老師,讓30個學生解答一道題目,然後檢查學生做的是否正確,你有下面幾個選擇:
-
第一種選擇:按順序逐個檢查,先檢查A,然後是B,之後是C、D。。。這中間如果有一個學生卡主,全班都會被耽誤。這種模式就好比,你用迴圈挨個處理socket,根本不具有併發能力。
-
第二種選擇:你建立30個分身,每個分身檢查一個學生的答案是否正確。 這種類似於為每一個使用者建立一個程式或者執行緒處理連線。
-
第三種選擇,你站在講臺上等,誰解答完誰舉手。這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然後繼續回到講臺上等。此時E、A又舉手,然後去處理E和A。
第一種就是阻塞IO模型,第三種就是I/O複用模型,Linux下的select、poll和epoll就是幹這個的。將使用者socket對應的fd註冊進epoll,然後epoll幫你監聽哪些socket上有訊息到達,這樣就避免了大量的無用操作。此時的socket應該採用非阻塞模式
。
這樣,整個過程只在呼叫select、poll、epoll這些呼叫的時候才會阻塞,收發客戶訊息是不會阻塞的,整個程式或者執行緒就被充分利用起來,這就是事件驅動
,所謂的reactor模式。
- 第三:單執行緒避免了執行緒切換和競態產生的消耗。
我們繼續來看Redis單執行緒卻很快的最後一條原因,在多執行緒開發中,存線上程的切換和競爭,這樣一來,是有時間的消耗的。對於需要磁碟I/O的程式來講,磁碟I/O是一個比較耗時的操作,所以對於需要進行磁碟I/O的程式,我們可以使用多執行緒,在某個執行緒進行I/O時,CPU切換到當前程式的其他執行緒執行,以此減少CPU的等待時間。
那麼問題來了。Redis的資料存放在記憶體中,將記憶體中的資料讀入CPU時,CPU不是依然需要等待嗎,為什麼不能在等待資料從記憶體讀入CPU期間執行其他執行緒,以此提高CPU的使用率呢?這個問題的答案很簡單,記憶體的讀些速度雖然比CPU慢很多,但是也是非常快的。CPU切換執行緒需要花費一定的時間,而多次切換執行緒所花費的時間,可能比直接使用單執行緒執行相同的任務,花費的時間要更多,這是非常不划算的。
單執行緒也會有一個問題
:對於每個命令的執行時間是有要求的。如果某個命令執行過長,會造成其他命令的阻塞,對於Redis這種高效能的服務來說是致命的,所以Redis是面向快速執行場景的資料庫。
2、支援多執行緒的Redis6.0
“Redis不是單執行緒嗎?怎麼又支援多執行緒了?”
相信學到了這裡,這已經不是一個問題了。
Redis6.0引入了多執行緒的特性,這個多執行緒是在哪裡呢?——是對處理網路請求過程採用了多執行緒。
Redis 6.0採用多個IO執行緒來處理網路請求,網路請求的解析可以由其他執行緒完成,然後把解析後的請求交由主執行緒進行實際的記憶體讀寫。提升網路請求處理的並行度,進而提升整體效能。
那麼多併發的執行緒安全問題存在嗎?——當然不存在。
Redis 的多 IO 執行緒只是用來處理網路請求的,對於命令的執行,Redis 仍然使用單執行緒來處理。
參考:
【1】:《Redis開發與運維》
【4】:Redis 和 I/O 多路複用
【5】:I/O多路複用技術(multiplexing)是什麼?
【6】:一文搞懂I/O多路複用及其技術
【7】:Redis為什麼是單執行緒的