redis 多路復用


這里“多路”指的是多個網絡連接,“復用”指的是復用同一個線程。

redis使用多路復用技術,可以處理並發的連接。非阻塞IO 內部實現采用epoll,采用了epoll+自己實現的簡單的事件框架。epoll中的讀、寫、關閉、連接都轉化成了事件,然后利用epoll的多路復用特性,絕不在io上浪費一點時間。

 

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

 

要弄清問題先要知道問題的出現原因

由於進程的執行過程是線性的(也就是順序執行),當我們調用低速系統I/O(read,write,accept等等),進程可能阻塞,此時進程就阻塞在這個調用上,不能執行其他操作。阻塞很正常, 接下來考慮這么一個問題:一個服務器進程和一個客戶端進程通信,服
務器端read(sockfd1,bud,bufsize),此時客戶端進程沒有發送數據,那么read(阻塞調用)將阻塞直到客戶端write(sockfd,but,size)發來數據。在一個客戶和服務器通信時這沒什么問題,當多個客戶與服務器通信時,若服務器阻塞於其中一個客戶sockfd1,當另一個客戶的數據到達套接字sockfd2時,服務器仍不能處理,仍然阻塞在read(sockfd1,...)上。此時問題就出現了,不能及時處理另一個客戶的服務,腫么辦?I/O多路復用來解決!

繼續上面的問題,有多個客戶連接,sockfd1、sockfd2、sockfd3..sockfdn同時監聽這n個客戶,當其中有一個發來消息時就從select的阻塞中返回,然后就調用read讀取收到消息的sockfd,然后又循環回select阻塞;這樣就不會因為阻塞在其中一個上而不能處理另一個客戶的消息。

Q:
那這樣子,在讀取socket1的數據時,如果其它socket有數據來,那么也要等到socket1讀取完了才能繼續讀取其它socket的數據吧。那不是也阻塞住了嗎?而且讀取到的數據也要開啟線程處理吧,那這和多線程I/O有什么區別呢?

A:
1.CPU本來就是線性的,不論什么都需要順序處理,並行只能是多核CPU。

2.I/O多路復用本來就是用來解決對多個I/O監聽時,一個I/O阻塞影響其他I/O的問題,跟多線程沒關系。

3.跟多線程相比較,線程切換需要切換到內核進行線程切換,需要消耗時間和資源。而I/O多路復用不需要切換線/進程,效率相對較高,特別是對高並發的應用nginx就是用I/O多路復用,故而性能極佳。但多線程編程邏輯和處理上比I/O多路復用簡單,而I/O多路復用處理起來較為復雜。

 

理解IO多路復用

什么是I/O 多路復用

I/O 多路復用其實是在單個線程中通過記錄跟蹤每一個sock(I/O流) 的狀態來管理多個I/O流。結合下圖可以清晰地理解I/O多路復用。

select, poll, epoll 都是I/O多路復用的具體的實現。epoll性能比其他幾者要好。redis中的I/O多路復用的所有功能通過包裝常見的select、epoll、evport和kqueue這些I/O多路復用函數庫來實現的。

 

多路分離函數select

IO多路復用模型是建立在內核提供的多路分離函數select基礎之上的,使用select函數可以避免同步非阻塞IO模型中輪詢等待的問題。

如上圖所示,用戶線程發起請求的時候,首先會將socket添加到select中,這時阻塞等待select函數返回。當數據到達時,select被激活,select函數返回,此時用戶線程才正式發起read請求,讀取數據並繼續執行。

從流程上來看,使用select函數進行I/O請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以后最大的優勢是用戶可以在一個線程內同時處理多個socket的I/O請求。用戶可以注冊多個socket,然后不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個I/O請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。

 

Reactor(反應器模式)

如上圖,I/O多路復用模型使用了Reactor設計模式實現了這一機制。通過Reactor的方式,可以將用戶線程輪詢I/O操作狀態的工作統一交給handle_events事件循環進行處理。用戶線程注冊事件處理器之后可以繼續執行做其他的工作(異步),而Reactor線程負責調用內核的select函數檢查socket狀態。當有socket被激活時,則通知相應的用戶線程(或執行用戶線程的回調函數),執行handle_event進行數據讀取、處理的工作。由於select函數是阻塞的,因此多路I/O復用模型也被稱為異步阻塞I/O模型。注意,這里的所說的阻塞是指select函數執行時線程被阻塞,而不是指socket。一般在使用I/O多路復用模型時,socket都是設置為NONBLOCK的,不過這並不會產生影響,因為用戶發起I/O請求時,數據已經到達了,用戶線程一定不會被阻塞。

 

總結

I/O 多路復用模型是利用select、poll、epoll可以同時監察多個流的 I/O 事件的能力,在空閑的時候,會把當前線程阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態中喚醒,於是程序就會輪詢一遍所有的流(epoll是只輪詢那些真正發出了事件的流),依次順序的處理就緒的流,這種做法就避免了大量的無用操作。這里“多路”指的是多個網絡連接,“復用”指的是復用同一個線程。采用多路 I/O 復用技術可以讓單個線程高效的處理多個連接請求(盡量減少網絡IO的時間消耗),且Redis在內存中操作數據的速度非常快(內存內的操作不會成為這里的性能瓶頸),主要以上兩點造就了Redis具有很高的吞吐量。

 

參考:

https://baijiahao.baidu.com/s?id=1624003934114185747&wfr=spider&for=pc

https://www.cnblogs.com/syyong/p/6231326.html

https://blog.csdn.net/happy_wu/article/details/80052617

https://blog.csdn.net/tanswer_/article/details/70196139

 


免責聲明!

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



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