衆所周知,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 仍然使用單線程來處理。