自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么單線程的 Redis 能那么快?

數(shù)據(jù)庫 Redis
今天,我們來探討一個(gè)很多人都很關(guān)心的問題:“為什么單線程的 Redis 能那么快?”

今天,我們來探討一個(gè)很多人都很關(guān)心的問題:“為什么單線程的 Redis 能那么快?”

首先,我要和你厘清一個(gè)事實(shí),我們通常說,Redis 是單線程,主要是指 Redis 的網(wǎng)絡(luò) IO 和鍵值對讀寫是由一個(gè)線程來完成的,這也是 Redis 對外提供鍵值存儲(chǔ)服務(wù)的主要流程。但 Redis 的其他功能,比如持久化、異步刪除、集群數(shù)據(jù)同步等,其實(shí)是由額外的線程執(zhí)行的。

所以,嚴(yán)格來說,Redis 并不是單線程,但是我們一般把 Redis 稱為單線程高性能,這樣顯得“酷”些。接下來,我也會(huì)把 Redis 稱為單線程模式。而且,這也會(huì)促使你緊接著提問:“為什么用單線程?為什么單線程能這么快?”

要弄明白這個(gè)問題,我們就要深入地學(xué)習(xí)下 Redis 的單線程設(shè)計(jì)機(jī)制以及多路復(fù)用機(jī)制。之后你在調(diào)優(yōu) Redis 性能時(shí),也能更有針對性地避免會(huì)導(dǎo)致 Redis 單線程阻塞的操作,例如執(zhí)行復(fù)雜度高的命令。

好了,話不多說,接下來,我們就先來學(xué)習(xí)下 Redis 采用單線程的原因。

Redis 為什么用單線程?

要更好地理解 Redis 為什么用單線程,我們就要先了解多線程的開銷。

多線程的開銷

多線程可以增加系統(tǒng)吞吐率,多線程機(jī)制可以將一個(gè)程序分為多個(gè)獨(dú)立運(yùn)行的線程,每個(gè)線程可以同時(shí)執(zhí)行不同任務(wù),避免了任務(wù)之間的互相等待,提高了系統(tǒng)的響應(yīng)速度。通過合理管理和調(diào)度這些線程,可以更好地利用計(jì)算機(jī)的處理能力,在較短時(shí)間內(nèi)處理更多的請求。

多線程還有助于提高系統(tǒng)的擴(kuò)展性。通過將任務(wù)拆分為多個(gè)子任務(wù),每個(gè)線程負(fù)責(zé)執(zhí)行其中一部分,可以更容易地將工作負(fù)載分配到多個(gè)處理單元上。這樣,在需要擴(kuò)展系統(tǒng)處理能力時(shí),只需增加更多的線程,而不需要修改整體架構(gòu)或重新設(shè)計(jì)系統(tǒng)。這種可伸縮性使得系統(tǒng)在應(yīng)對不斷增長的需求時(shí)更具競爭力。

下面的左圖是我們采用多線程時(shí)所期待的結(jié)果。

但是,請你注意,通常情況下,在我們采用多線程后,如果沒有良好的系統(tǒng)設(shè)計(jì),實(shí)際得到的結(jié)果,其實(shí)是右圖所展示的那樣。我們剛開始增加線程數(shù)時(shí),系統(tǒng)吞吐率會(huì)增加,但是,再進(jìn)一步增加線程時(shí),系統(tǒng)吞吐率就增長遲緩了,有時(shí)甚至還會(huì)出現(xiàn)下降的情況。

線程數(shù)與系統(tǒng)吞吐率

為什么會(huì)出現(xiàn)這種情況呢?一個(gè)關(guān)鍵的瓶頸在于,系統(tǒng)中通常會(huì)存在被多線程同時(shí)訪問的共享資源,比如一個(gè)共享的數(shù)據(jù)結(jié)構(gòu)。當(dāng)有多個(gè)線程要修改這個(gè)共享資源時(shí),為了保證共享資源的正確性,就需要有額外的機(jī)制進(jìn)行保證,而這個(gè)額外的機(jī)制,就會(huì)帶來額外的開銷。

拿 Redis 來說,在上節(jié)課中,我提到過,Redis 有 List 的數(shù)據(jù)類型,并提供出隊(duì)(LPOP)和入隊(duì)(LPUSH)操作。假設(shè) Redis 采用多線程設(shè)計(jì),如下圖所示,現(xiàn)在有兩個(gè)線程 A 和 B,線程 A 對一個(gè) List 做 LPUSH 操作,并對隊(duì)列長度加 1。同時(shí),線程 B 對該 List 執(zhí)行 LPOP 操作,并對隊(duì)列長度減 1。為了保證隊(duì)列長度的正確性,Redis 需要讓線程 A 和 B 的 LPUSH 和 LPOP 串行執(zhí)行,這樣一來,Redis 可以無誤地記錄它們對 List 長度的修改。否則,我們可能就會(huì)得到錯(cuò)誤的長度結(jié)果。這就是多線程編程模式面臨的共享資源的并發(fā)訪問控制問題。

多線程并發(fā)訪問Redis

并發(fā)訪問控制一直是多線程開發(fā)中的一個(gè)難點(diǎn)問題,如果沒有精細(xì)的設(shè)計(jì),比如說,只是簡單地采用一個(gè)粗粒度互斥鎖,就會(huì)出現(xiàn)不理想的結(jié)果:即使增加了線程,大部分線程也在等待獲取訪問共享資源的互斥鎖,并行變串行,系統(tǒng)吞吐率并沒有隨著線程的增加而增加。

而且,采用多線程開發(fā)一般會(huì)引入同步原語來保護(hù)共享資源的并發(fā)訪問,這也會(huì)降低系統(tǒng)代碼的易調(diào)試性和可維護(hù)性。為了避免這些問題,Redis 直接采用了單線程模式。

講到這里,你應(yīng)該已經(jīng)明白了“Redis 為什么用單線程”,那么,接下來,我們就來看看,為什么單線程 Redis 能獲得高性能。

單線程 Redis 為什么那么快?

通常來說,單線程的處理能力要比多線程差很多,但是 Redis 卻能使用單線程模型達(dá)到每秒數(shù)十萬級別的處理能力,這是為什么呢?其實(shí),這是 Redis 多方面設(shè)計(jì)選擇的一個(gè)綜合結(jié)果。

一方面,Redis 的大部分操作在內(nèi)存上完成,再加上它采用了高效的數(shù)據(jù)結(jié)構(gòu),例如哈希表和跳表,這是它實(shí)現(xiàn)高性能的一個(gè)重要原因。另一方面,就是 Redis 采用了多路復(fù)用機(jī)制,使其在網(wǎng)絡(luò) IO 操作中能并發(fā)處理大量的客戶端請求,實(shí)現(xiàn)高吞吐率。接下來,我們就重點(diǎn)學(xué)習(xí)下多路復(fù)用機(jī)制。

首先,我們要弄明白網(wǎng)絡(luò)操作的基本 IO 模型和潛在的阻塞點(diǎn)。畢竟,Redis 采用單線程進(jìn)行 IO,如果線程被阻塞了,就無法進(jìn)行多路復(fù)用了。

1.基本 IO 模型與阻塞點(diǎn)

(1) 阻塞模式

其實(shí)下面所說的Socket 網(wǎng)絡(luò)模型在早期的時(shí)候是沒有非阻塞設(shè)置的,因此會(huì)造成一直等待,也就阻塞了。

這也就是我們所說的BIO網(wǎng)絡(luò)模型,關(guān)于BIO這個(gè)最基本的IO模型,具體是怎么阻塞的想必大家都比較清楚,這里不再過多解釋,本號其他文章有關(guān)于io模型的介紹。

(2) 非阻塞模式

Socket 網(wǎng)絡(luò)模型的非阻塞模式設(shè)置,主要體現(xiàn)在三個(gè)關(guān)鍵的函數(shù)調(diào)用上,如果想要使用 socket 非阻塞模式,就必須要了解這三個(gè)函數(shù)的調(diào)用返回類型和設(shè)置模式。接下來,我們就重點(diǎn)學(xué)習(xí)下它們。

在 socket 模型中,不同操作調(diào)用后會(huì)返回不同的套接字類型。socket() 方法會(huì)返回主動(dòng)套接字,然后調(diào)用 listen() 方法,將主動(dòng)套接字轉(zhuǎn)化為監(jiān)聽套接字,此時(shí),可以監(jiān)聽來自客戶端的連接請求。最后,調(diào)用 accept() 方法接收到達(dá)的客戶端連接,并返回已連接套接字。

Redis套接字類型與非阻塞設(shè)置

針對監(jiān)聽套接字,我們可以設(shè)置非阻塞模式:當(dāng) Redis 調(diào)用 accept() 但一直未有連接請求到達(dá)時(shí),Redis 線程可以返回處理其他操作,而不用一直等待。但是,你要注意的是,調(diào)用 accept() 時(shí),已經(jīng)存在監(jiān)聽套接字了。

雖然 Redis 線程可以不用繼續(xù)等待,但是總得有機(jī)制繼續(xù)在監(jiān)聽套接字上等待后續(xù)連接請求,并在有請求時(shí)通知 Redis。

類似的,我們也可以針對已連接套接字設(shè)置非阻塞模式:Redis 調(diào)用 recv() 后,如果已連接套接字上一直沒有數(shù)據(jù)到達(dá),Redis 線程同樣可以返回處理其他操作。我們也需要有機(jī)制繼續(xù)監(jiān)聽該已連接套接字,并在有數(shù)據(jù)達(dá)到時(shí)通知 Redis。

這樣才能保證 Redis 線程,既不會(huì)像基本 IO 模型中一直在阻塞點(diǎn)等待,也不會(huì)導(dǎo)致 Redis 無法處理實(shí)際到達(dá)的連接請求或數(shù)據(jù)。

到此,Linux 中的 IO 多路復(fù)用機(jī)制就要登場了。

2.基于多路復(fù)用的高性能 I/O 模型

Linux 中的 IO 多路復(fù)用機(jī)制是指一個(gè)線程處理多個(gè) IO 流,就是我們經(jīng)常聽到的 select/epoll 機(jī)制。簡單來說,在 Redis 只運(yùn)行單線程的情況下,該機(jī)制允許內(nèi)核中,同時(shí)存在多個(gè)監(jiān)聽套接字和已連接套接字。內(nèi)核會(huì)一直監(jiān)聽這些套接字上的連接請求或數(shù)據(jù)請求。一旦有請求到達(dá),就會(huì)交給 Redis 線程處理,這就實(shí)現(xiàn)了一個(gè) Redis 線程處理多個(gè) IO 流的效果。

下圖就是基于多路復(fù)用的 Redis IO 模型。圖中的多個(gè) FD 就是剛才所說的多個(gè)套接字。Redis 網(wǎng)絡(luò)框架調(diào)用 epoll 機(jī)制,讓內(nèi)核監(jiān)聽這些套接字。此時(shí),Redis 線程不會(huì)阻塞在某一個(gè)特定的監(jiān)聽或已連接套接字上,也就是說,不會(huì)阻塞在某一個(gè)特定的客戶端請求處理上。正因?yàn)榇?,Redis 可以同時(shí)和多個(gè)客戶端連接并處理請求,從而提升并發(fā)性。

基于多路復(fù)用的Redis高性能IO模型

為了在請求到達(dá)時(shí)能通知到 Redis 線程,select/epoll 提供了基于事件的回調(diào)機(jī)制,即針對不同事件的發(fā)生,調(diào)用相應(yīng)的處理函數(shù)。

那么,回調(diào)機(jī)制是怎么工作的呢?其實(shí),select/epoll 一旦監(jiān)測到 FD 上有請求到達(dá)時(shí),就會(huì)觸發(fā)相應(yīng)的事件。

這些事件會(huì)被存放在一個(gè)事件隊(duì)列中,Redis 單線程會(huì)不斷地處理這個(gè)事件隊(duì)列。這種方法使得 Redis 不必持續(xù)輪詢是否有請求發(fā)生,有效地減少了對 CPU 資源的浪費(fèi)。同時(shí),Redis 在處理事件隊(duì)列中的事件時(shí),會(huì)觸發(fā)相應(yīng)的處理函數(shù),從而實(shí)現(xiàn)了基于事件的回調(diào)機(jī)制。由于 Redis 不斷地處理事件隊(duì)列,因此能夠迅速響應(yīng)客戶端請求,提高了 Redis 的響應(yīng)性能。

為了更好地理解,我以連接請求和讀數(shù)據(jù)請求為例,進(jìn)一步解釋這個(gè)過程。

這兩個(gè)請求對應(yīng)著 Accept 事件和 Read 事件,Redis 分別注冊了 accept 和 get 回調(diào)函數(shù)來處理這兩類事件。當(dāng) Linux 內(nèi)核檢測到連接請求或讀取數(shù)據(jù)請求時(shí),就會(huì)觸發(fā) Accept 事件和 Read 事件,此時(shí)內(nèi)核會(huì)回調(diào) Redis 的相應(yīng) accept 和 get 函數(shù)來處理這些事件。

這就好比病人前往醫(yī)院就醫(yī)。在醫(yī)生實(shí)際進(jìn)行診斷之前,每位病人(類似于請求)都需要經(jīng)歷分診、測量體溫、填寫登記表等過程。如果所有這些工作都由醫(yī)生親自完成,那醫(yī)生的效率將會(huì)很低。因此,醫(yī)院通常設(shè)置了分診臺(tái),分診臺(tái)會(huì)專門處理這些在診斷之前的任務(wù)(類似于 Linux 內(nèi)核監(jiān)聽請求),然后再將病人交給醫(yī)生進(jìn)行實(shí)際診斷。這種方式,即使只有一個(gè)醫(yī)生(相當(dāng)于 Redis 單線程),效率也能夠顯著提高。

需要注意的是,多路復(fù)用機(jī)制是適用于各種操作系統(tǒng)的,即使你的應(yīng)用在不同操作系統(tǒng)上運(yùn)行,多路復(fù)用機(jī)制依然有效。這是因?yàn)槎嗦窂?fù)用機(jī)制的具體實(shí)現(xiàn)方式有多種,包括基于 Linux 系統(tǒng)的 select 和 epoll 實(shí)現(xiàn)、基于 FreeBSD 的 kqueue 實(shí)現(xiàn),以及基于 Solaris 的 evport 實(shí)現(xiàn),因此你可以根據(jù) Redis 運(yùn)行的實(shí)際操作系統(tǒng),選擇合適的多路復(fù)用實(shí)現(xiàn)方式。

小結(jié)

在前面的學(xué)習(xí)中,我們重點(diǎn)探討了 Redis 線程背后的三個(gè)關(guān)鍵問題,即“Redis是否真的只使用單線程?”、“為什么堅(jiān)持使用單線程?”以及“為何Redis的單線程如此高效?”

現(xiàn)在,我們已經(jīng)理解,Redis的單線程指的是它采用單一線程來處理網(wǎng)絡(luò)I/O和數(shù)據(jù)讀寫操作,而采用單線程的核心原因之一是為了避免多線程開發(fā)中的復(fù)雜并發(fā)控制問題。Redis的單線程性能卓越,與其采用的多路復(fù)用I/O模型密切相關(guān),因?yàn)檫@有助于規(guī)避accept()和send()/recv()等潛在的網(wǎng)絡(luò)I/O操作阻塞問題。

通過深入理解這些問題,您已經(jīng)走在了許多人的前沿。如果您的朋友或同事還對這些問題感到困惑,不妨與他們分享這些見解,幫助他們消除疑慮。

此外,我來透露一下,您可能已經(jīng)注意到,于2020年5月,Redis 6.0發(fā)布了其穩(wěn)定版本,其中引入了多線程模型。那么,這個(gè)多線程模型與我們在本課程中討論的I/O模型是否有關(guān)系?它是否會(huì)引入復(fù)雜的并發(fā)控制問題?又是否將如何提升Redis 6.0的性能表現(xiàn)?關(guān)于這些問題,我將在接下來的課程中為您詳細(xì)介紹。

責(zé)任編輯:趙寧寧 來源: 碼農(nóng)本農(nóng)
相關(guān)推薦

2023-03-21 08:02:36

Redis6.0IO多線程

2019-06-17 14:20:51

Redis數(shù)據(jù)庫Java

2020-06-11 09:35:39

Redis單線程Java

2020-10-30 16:20:38

Redis單線程高并發(fā)

2019-02-18 08:10:53

2021-03-03 08:01:58

Redis多線程程序

2025-01-17 08:23:33

2019-05-07 09:44:45

Redis高并發(fā)模型

2023-08-17 14:12:17

2019-05-06 11:12:18

Redis高并發(fā)單線程

2020-11-09 09:33:37

多線程

2019-04-02 11:20:48

Redis高并發(fā)單線程

2020-11-17 10:20:53

Redis多線程單線程

2023-02-07 08:18:34

單線程Redis內(nèi)存

2020-10-16 16:00:50

Redis單線程數(shù)據(jù)庫

2022-01-04 11:11:32

Redis單線程Reactor

2020-07-29 08:06:30

Kafka MQ消息

2025-04-24 08:15:00

Redis單線程線程

2021-12-28 09:50:18

Redis單線程高并發(fā)

2023-06-08 18:25:40

Doris場景查詢
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號