Redis I/O 多路復用技術原理


引言

Redis 是一個單線程卻性能非常好的內存數據庫, 主要用來作為緩存系統。 Redis 采用網絡 I/O 多路復用技術來保證在多個連接時,系統的高吞吐量(TPS)。

系統吞吐量(TPS)指的是系統在單位時間內可處理的事務的數量,是用於衡量系統性能的重要指標。影響系統吞吐量的因素很多,包括並發數和系統資源(CPU、內存、系統I/O操作、外部接口)等,系統資源等這些因素可以用平均響應時間指標來衡量

五種 I/O 模型

要理解 Redis 采用網絡 I/O 多路復用技術,就得先了解五種 I/O 模型,如下:

  • 阻塞 I/O 模型

    最傳統的一種 I/O 模型,即在讀寫數據過程中會發生阻塞現象。

    當用戶線程發出 I/O 請求之后,內核會去查看數據是否就緒,如果沒有就緒就會等待數據就緒,而用戶線程就會處於阻塞狀態(block),用戶線程交出 CPU。當數據就緒之后,內核會將數據拷貝到用戶線程,並返回結果給用戶線程,用戶線程才解除阻塞狀態。

    舉例如下:

    data = socket.read();
    

    如果數據沒有就緒,用戶線程就會一直阻塞在 read 方法。

  • 非阻塞 I/O 模型

    當用戶線程發起一個 read 操作后,並不需要等待,而是馬上就得到了一個結果。如果結果是一個 error 時,它就知道數據還沒有准備好,於是它可以再次發送 read 操作。一旦內核中的數據准備好了,並且又再次收到了用戶線程的請求,那么它馬上就將數據拷貝到了用戶線程,然后返回。

    所以事實上,在非阻塞 I/O 模型中,用戶線程需要不斷地詢問內核數據是否就緒,也就說非阻塞 I/O 不會交出 CPU,而會一直占用 CPU,從而導致 CPU 占用率非常高。

  • 多路復用 I/O 模型

    多路復用 I/O 模型是目前使用得比較多的 I/O 模型。

    在多路復用 I/O 模型中,會有一個線程不斷去輪詢多個 socket 的狀態,只有當 socket 真正有讀寫事件時,才真正調用實際的 I/O 讀寫操作。因為在多路復用 I/O 模型中,只需要使用一個線程就可以管理多個 socket,系統不需要建立新的進程或者線程,也不必維護這些線程和進程,並且只有在真正有 socket 讀寫事件進行時,才會使用 I/O 資源,所以它大大減少了資源占用(如 CPU)。

    會思考的你也許會想到,可以采用多線程+ 阻塞 I/O 達到類似的效果,但是由於在多線程 + 阻塞 I/O 中,每個 socket對應一個線程,這樣會造成很大的資源占用,並且尤其是對於長連接來說,線程的資源一直不會釋放,如果后面陸續有很多連接的話,就會造成性能上的瓶頸。

    而多路復用 I/O 模式,通過一個線程就可以管理多個 socket,只有當 socket 真正有讀寫事件發生才會占用資源來進行實際的讀寫操作。因此,多路復用IO比較適合連接數比較多的情況。

    另外,多路復用 I/O 為何比非阻塞 I/O 模型的效率高是因為在非阻塞 I/O 中,不斷地詢問 socket 狀態時通過用戶線程去進行的,而在多路復用 I/O 中,輪詢每個 socket 狀態是內核在進行的,這個效率要比用戶線程要高的多。

    值得注意的是,多路復用 I/O 模型是通過輪詢的方式來檢測是否有事件到達,並且對到達的事件逐一進行響應。因此對於多路復用 I/O 模型來說,一旦事件響應太慢,那么就會導致后續的事件遲遲得不到處理,並且會影響新的事件輪詢。

    這就是說,如果 Redis 每條命令執行如果占用大量時間,就會造成其他線程阻塞,對於 Redis 這種高性能服務是致命的,所以 Redis 是面向高速執行的數據庫。

  • 信號驅動 I/O 模型

    在信號驅動 I/O 模型中,當用戶線程發起一個 I/O 請求操作,會給對應的 socket 注冊一個信號函數,然后用戶線程會繼續執行,當內核數據就緒時會發送一個信號給用戶線程,用戶線程接收到信號之后,便在信號函數中調用 I/O 讀寫操作來進行實際的 I/O 請求操作。

    這個一般用於 UDP 中,對 TCP 套接口幾乎是沒用的,原因是該信號產生得過於頻繁,並且該信號的出現並沒有告訴我們發生了什么事情。

  • 異步 I/O 模型

    異步 I/O 模型才是最理想的 I/O 模型,在異步 I/O 模型中,當用戶線程發起 read 操作之后,立刻就可以開始去做其它的事。而另一方面,從內核的角度,當它收到一個 asynchronous read 之后,它會立刻返回,說明 read 請求已經成功發起了,因此不會對用戶線程產生任何阻塞(block)。

    然后,內核會等待數據准備完成,然后將數據拷貝到用戶線程,當這一切都完成之后,內核會給用戶線程發送一個信號,告訴它 read 操作完成了。

    也就是說,用戶線程完全不需要關心實際的整個 I/O 操作是如何進行的,只需要先發起一個請求,當接收內核返回的成功信號時表示 I/O 操作已經完成,可以直接去使用數據了。

這五種 I/O 模型中,前面四種 I/O 模型實際上都屬於同步 I/O,只有最后一種是真正的異步 I/O,因為無論是多路復用 I/O模型還是信號驅動 I/O 模型,I/O 操作的第 2 個階段都會引起用戶線程阻塞,也就是內核進行數據拷貝的過程都會讓用戶線程阻塞。

為何 Redis 要使用 I/O 多路復用技術

現在來回答這個問題就 so easy 啦!

首先,Redis 是單線程架構,所有的命令操作都是先進入隊列,然后一個一個按照順序線性執行的,但是由於讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會導致某一文件的 I/O 阻塞導致整個進程無法對其它客戶提供服務,而采用 I/O 多路復用技術就是為了解決這個問題。

Redis 為何不采用異步 I/O 模型,這個不是效率更高嗎?這玩意兒在多線程下才能發揮功效,而 Redis 是單線程架構哈。

epoll 是什么

epoll 其實只是眾多實現 I/O多路復用模型的技術當中的一種而已,但是相比其他 I/O 多路復用技術(select, poll等),epoll有諸多優點(Redis 也支持 select 和 poll,默認使用 epoll)

  1. epoll 沒有最大並發連接的限制,上限是最大可以打開文件的數目,這個數字一般遠大於 2048,
  2. 效率提升, epoll 最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中, epoll 的效率就會遠遠高於 select 和 poll
  3. 內存拷貝, epoll 直接使用的 "共享內存",可以跳過傳統的內存拷貝操作,效率更高

總結

Redis 采用 I/O 多路復用技術(epoll)是因為 Redis 是單線程架構,是為了避免網絡 I/O 讀寫操作阻塞整個進程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM