面試時說Redis是單執行緒的,被噴慘了!

2022-03-17 13:44:12 字數 3019 閱讀 8160

到底是什麼樣的,各位看官請跟小萊一起往下:

圖注:思維導圖

反應器模式,你可能不太認識,如果看過上篇文章的話應該會有點印象。涉及到 redis 執行緒它是乙個繞不過去的話題。

在講反應器模式前,這裡有必要提一下傳統阻塞io模型的處理方式。

在傳統阻塞io模型中,由乙個獨立的 acceptor 執行緒來監聽客戶端的連線,每當有客戶端請求過來時,它就會為客戶端分配乙個新的執行緒來進行處理。當同時有多個請求過來,服務端對應的就會分配相應數量的執行緒。這就會導致cpu頻繁切換,浪費資源。

有的連線請求過來不做任何事情,但服務端還會分配對應的執行緒,這樣就會造成不必要的執行緒開銷。這就好比你去餐廳吃飯,你拿著選單看了半天發現真他娘的貴,然後你就走人了。這段時間等你點菜的服務員就相當於乙個對應的執行緒,你要點菜可以看作乙個連線請求。

同時,每次建立連線後,當執行緒呼叫讀寫方法時,執行緒會被阻塞,直到有資料可讀可寫, 在此期間執行緒不能做其它事情。還是上邊餐廳吃飯的例子,你出去轉了一圈發現還是這家價效比最高。回到這家餐廳又拿著選單看了半天,服務員也在旁邊等你點完菜為止。這個過程中服務員什麼也不能做,只能這麼幹等著,這個過程相當於阻塞。

你看這樣的方式,每來乙個請求就要分配乙個執行緒,並且還得阻塞地等執行緒處理完。有的請求還只是過來連線下,什麼操作也不幹,還得為它分配乙個執行緒,對伺服器資源要求那得多高啊。遇到高併發場景,不敢想象。對於連線數目比較小的的固定架構倒是可以考慮。

你可能了解過一種通過執行緒池優化的解決方案,採用執行緒池和任務佇列的方式。這種被稱作偽非同步io模型。

當有客戶端接入時,將客戶端的請求封裝成乙個 task 投遞到後端線程池中來處理。執行緒池維護乙個訊息佇列和多個活躍執行緒,對訊息佇列中的任務進行處理。

這種解決方案,避免了為每個請求建立乙個執行緒導致的執行緒資源耗盡問題。但是底層仍然是同步阻塞模型。如果執行緒池內的所有執行緒都阻塞了,那麼對於更多請求就無法響應了。因此這種模式會限制最大連線數,並不能從根本上解決問題。

我們繼續用上邊的餐廳來舉例,餐廳老闆在經營了一段時間後,顧客多了起來,原本店裡的5個服務員一對一服務的話根本對付不過來。於是老闆採用5個人執行緒池的方式。服務員服務完乙個客人後立刻去服務另乙個。

這時問題出現了,有的客人點菜特別慢,服務員就得等待很長時間,直到客人點完為止。如果5個客人都點的特別慢的話,這5個服務員就得一直等下去,就會導致其餘的顧客沒有人服務的狀態。這就是我們上邊所說的執行緒池所有執行緒都被阻塞的情況。

那麼這種問題該如何解決呢?別急, reactor 模式就要出場了。

reactor 模式的基本設計思想是基於i/o復用模型來實現的。

這裡說下i/o復用模型。和傳統io多執行緒阻塞不同,i/o復用模型中多個連線共用乙個阻塞物件,應用程式只需要在乙個阻塞物件等待。當某個連線有新的資料可以處理時,作業系統通知應用程式,執行緒從阻塞狀態返回,開始進行業務處理。

什麼意思呢?餐廳老闆也發現了顧客點餐慢的問題,於是他採用了一種大膽的方式,只留了乙個服務員。當客人點餐的時候,這個服務員就去招待別的客人,客人點好餐後直接喊服務員來進行服務。這裡的顧客和服務員可以分別看作多個連線和乙個執行緒。服務員阻塞在乙個顧客那裡,當有別的顧客點好餐後,她就立刻去服務其他的顧客。

了解了 reactor 的設計思想後,我們再來看下今天的主角單 reactor 單執行緒的實現方案:

reactor 通過 i/o復用程式監控客戶端請求事件,收到事件後通過任務分派器進行分發。

針對建立連線請求事件,通過 acceptor 處理,並建立對應的 handler 負責後續業務處理。

針對非連線事件,reactor 會呼叫對應的 handler 完成 read->業務處理->write 處理流程,並將結果返回給客戶端。

整個過程都在乙個執行緒裡完成。

了解了 reactor 模式後,你可能會有乙個疑問,這個和我們今天的主題有什麼關係呢。可能你不知道的是,redis 是基於 reactor 單執行緒模式來實現的。

io多路復用程式接收到使用者的請求後,全部推送到乙個佇列裡,交給檔案分派器。對於後續的操作,和在 reactor 單執行緒實現方案裡看到的一樣,整個過程都在乙個執行緒裡完成,因此 redis 被稱為是單執行緒的操作。

對於單執行緒的 redis 來說,基於記憶體,且命令操作時間複雜度低,因此讀寫速率是非常快的。

redis6 版本中引入了多執行緒。上邊已經提到過 redis 單執行緒處理有著很快的速度,那為什麼還要引入多執行緒呢?單執行緒的瓶頸在什麼地方?

我們先來看第二個問題,在 redis 中,單執行緒的效能瓶頸主要在網路io操作上。也就是在讀寫網路 read/write 系統呼叫執行期間會占用大部分 cpu 時間。如果你要對一些大的鍵值對進行刪除操作的話,在短時間內是刪不完的,那麼對於單執行緒來說就會阻塞後邊的操作。

回想下上邊講得 reactor 模式中單執行緒的處理方式。針對非連線事件,reactor 會呼叫對應的 handler 完成 read->業務處理->write 處理流程,也就是說這一步會造成效能上的瓶頸。

redis 在設計上採用將網路資料讀寫和協議解析通過多執行緒的方式來處理,對於命令執行來說,仍然使用單執行緒操作。

reactor模式

單執行緒時代

多執行緒時代

面試 為什麼Redis是單執行緒

先給下官網回答 分析 多執行緒使用場景 a充分利用多核cpu b 檔案或者網路io密集型 任務排程 1 redis在linux上 使用管道每秒可以處理百萬請求 如果都是時間複雜度o n 或o log n 命令 單核足以支撐 所以a不滿足 2 redis是針對記憶體操作 所以檔案io不滿足 redis...

redis底層是單執行緒

一直有個錯覺就是,redis底層命令是併發執行的,但今天檢視若干資料後發現,redis底層是單執行緒的,也就是說多客戶端向redis伺服器傳送操作命令,redis底層是序列執行的,這樣不禁懷疑這樣不是很慢嗎?後經過進一步 得出結論 1 單執行緒並不一定比多執行緒慢,多執行緒需要不停切換上下文,本身就...

redis的單執行緒

1 完全基於記憶體,絕大部分請求是純粹的記憶體操作,非常快速。資料存在記憶體中,類似於hashmap,hashmap的優勢就是查詢和操作的時間複雜度都是o 1 2 資料結構簡單,對資料操作也簡單,redis中的資料結構是專門進行設計的 3 採用單執行緒,避免了不必要的上下文切換和競爭條件,也不存在多...