每天一個知識點:單線程 Redis 為什么能那么快?

今天,我們來探討一個很多人都很關心的問題:“為什么單線程的 Redis 能那么快?”
首先,要厘清一個事實,我們通常說,Redis 是單線程,主要是指 Redis 的網(wǎng)絡 IO 和鍵值對讀寫是由一個線程來完成的,這也是 Redis 對外提供鍵值存儲服務的主要流程。但 Redis 的其他功能,比如持久化、異步刪除、集群數(shù)據(jù)同步等,其實是由額外的線程執(zhí)行的。

Redis 為什么用單線程?

  • 本身是基于內(nèi)存的,CPU并不是其主要的性能瓶頸。
  • 對于一些共享資源的訪問使用多線程的話,常常需要一些別的機制來保證數(shù)據(jù)庫的一致性(例如鎖機制),會造成一定的資源浪費。

多線程的開銷

  • 在有合理的資源分配的情況下,可以增加系統(tǒng)中處理請求操作的資源實體,進而提升系統(tǒng)能夠同時處理的請求數(shù),即吞吐率。(提高并發(fā)度,并不一定非得采用多線程的方式;采用多路復用、事件驅(qū)動的方式也可以。)
  • 如果沒有良好的系統(tǒng)設計,實際得到的結果,其實是剛開始增加線程數(shù)時,系統(tǒng)吞吐率會增加,但是,再進一步增加線程時,系統(tǒng)吞吐率就增長遲緩了,有時甚至還會出現(xiàn)下降的情況。為什么會出現(xiàn)這種情況呢?系統(tǒng)中通常會存在被多線程同時訪問的共享資源(1、線程之間的競爭2、線程切換),比如一個共享的數(shù)據(jù)結構。當有多個線程要修改這個共享資源時,為了保證共享資源的正確性,就需要有額外的機制進行保證,而這個額外的機制,就會帶來額外的開銷。
  • 共享資源的并發(fā)訪問控制問題。如果沒有精細的設計,比如說,只是簡單地采用一個粗粒度互斥鎖,就會出現(xiàn)不理想的結果:即使增加了線程,大部分線程也在等待獲取訪問共享資源的互斥鎖,并行變串行,系統(tǒng)吞吐率并沒有隨著線程的增加而增加。采用多線程開發(fā)一般會引入同步原語來保護共享資源的并發(fā)訪問,這也會降低系統(tǒng)代碼的易調(diào)試性和可維護性。

單線程 Redis 為什么那么快?

  • 大部分操作在內(nèi)存上完成。
  • 采用了高效的數(shù)據(jù)結構,例如哈希表和跳表,這是它實現(xiàn)高性能的一個重要原因。
  • 采用了多路復用機制,使其在網(wǎng)絡 IO 操作中能并發(fā)處理大量的客戶端請求,實現(xiàn)高吞吐率。

基本 IO 模型與阻塞點

  1. 監(jiān)聽客戶端請求(bind/listen);
  2. 與客戶端建立連接(accept)— 阻塞點;
  3. 從socket中讀取請求(recv)— 阻塞點;
  4. 解析客戶端請求(parse);
  5. 向socket中寫回數(shù)據(jù)(send)。

基于多路復用的高性能 I/O 模型

  • Linux 中的 IO 多路復用機制是指一個線程處理多個 IO 流,就是我們經(jīng)常聽到的 select/epoll 機制。簡單來說,在 Redis 只運行單線程的情況下,該機制允許內(nèi)核中,同時存在多個監(jiān)聽套接字和已連接套接字。內(nèi)核會一直監(jiān)聽這些套接字上的連接請求或數(shù)據(jù)請求。一旦有請求到達,就會交給 Redis 線程處理,這就實現(xiàn)了一個 Redis 線程處理多個 IO 流的效果。
  • Redis IO 多路復用機制總結為:
    • 基于linux select/epoll
    • 內(nèi)核可同時監(jiān)聽多個監(jiān)聽套接字和多個已連接套接字
    • 一旦內(nèi)核監(jiān)聽到套接字上有數(shù)據(jù)返回,立刻交給redis線程處理數(shù)據(jù)
  • 為了在請求到達時能通知到 Redis 線程,select/epoll 提供了基于事件的回調(diào)機制,即針對不同事件的發(fā)生,調(diào)用相應的處理函數(shù)。所有添加到epoll中的事件都會與設備(網(wǎng)卡)驅(qū)動程序建立回調(diào)關系,也就是說,當相應的事件發(fā)生時會調(diào)用這個回調(diào)方法。這個回調(diào)方法在內(nèi)核中叫ep_poll_callback,它會將發(fā)生的事件添加到rdlist雙鏈表中。
  • 回調(diào)機制是怎么工作的呢?select/epoll 一旦監(jiān)測到 FD 上有請求到達時,就會觸發(fā)相應的事件。這些事件會被放進一個事件隊列,Redis 單線程對該事件隊列不斷進行處理。這樣一來,Redis 無需一直輪詢是否有請求實際發(fā)生,這就可以避免造成 CPU 資源浪費。同時,Redis 在對事件隊列中的事件進行處理時,會調(diào)用相應的處理函數(shù),這就實現(xiàn)了基于事件的回調(diào)。因為 Redis 一直在對事件隊列進行處理,所以能及時響應客戶端請求,提升 Redis 的響應性能。即所有添加到epoll中的事件都會與設備(網(wǎng)卡)驅(qū)動程序建立回調(diào)關系,也就是說,一旦監(jiān)測到fd上有請求到達時,就會觸發(fā)相應的事件,當相應的事件發(fā)生時會調(diào)用回調(diào)方法。這個回調(diào)方法在內(nèi)核中叫ep_poll_callback,它會將發(fā)生的事件添加到rdlist雙鏈表中。select 輪詢遍歷 文件對象的被監(jiān)控的事件(accept, read, write),一旦某個文件對象的監(jiān)控事件被觸發(fā)(讀或者寫或者請求就緒),滿足條件,這個事件就會被放到事件隊列進行處理,處理的過程就是調(diào)用對應的回調(diào)函數(shù)。
  • 不同的操作系統(tǒng),多路復用機制也是適用的。因為這個機制的實現(xiàn)有很多種,既有基于 Linux 系統(tǒng)下的 select 和 epoll 實現(xiàn),也有基于 FreeBSD 的 kqueue 實現(xiàn),以及基于 Solaris 的 evport 實現(xiàn),這樣,你可以根據(jù) Redis 實際運行的操作系統(tǒng),選擇相應的多路復用實現(xiàn)。

總結

  • Redis 單線程是指它對網(wǎng)絡 IO 和數(shù)據(jù)讀寫的操作采用了一個線程,而采用單線程的一個核心原因是避免多線程開發(fā)的并發(fā)控制問題。單線程的 Redis 也能獲得高性能,跟多路復用的 IO 模型密切相關,因為這避免了 accept() 和 send()/recv() 潛在的網(wǎng)絡 IO 操作阻塞點。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容