有沒有人和我一樣, 自打知道了redis, 就一直聽說什么redis單線程, 使用了多路復(fù)用等等. 天真的我以為多路復(fù)用是redis實(shí)現(xiàn)的技術(shù). 今天才發(fā)現(xiàn), 我被自己騙了, 多路復(fù)用是系統(tǒng)來實(shí)現(xiàn)的. 對(duì)不起自己的專業(yè)了.
為了引出多路復(fù)用, 我來大膽設(shè)想一下技術(shù)的發(fā)展路程.
前提
一個(gè)應(yīng)用程序, 想對(duì)外提供服務(wù), 一般都是通過建立套接字監(jiān)聽端口來實(shí)現(xiàn), 也就是socket. 在這個(gè)時(shí)候, 應(yīng)用對(duì)外提供服務(wù)的過程大概是這樣.
- 創(chuàng)建套接字
- 綁定端口號(hào)
- 開始監(jiān)聽
- 當(dāng)監(jiān)聽到連接時(shí), 調(diào)用系統(tǒng)read去讀取內(nèi)容, 但是讀取操作是阻塞的(也就是說,如果主線程處理read,就不能接收其他連接了, 所以只能開新的線程去處理這個(gè)事情)
畫個(gè)丑丑的流程圖:
問題分析
這個(gè)流程的問題很明顯, 會(huì)不停的創(chuàng)建線程, 當(dāng)然, 可以維護(hù)一個(gè)線程池. 但是線程之間的不停切換也是消耗資源的. 而且也不可能無限的創(chuàng)建線程. 那我如果想一個(gè)線程處理呢? 從上面流程圖能看的出來, 問題出在阻塞上面. 如果read操作可以立刻返回結(jié)果, 如果沒有讀到數(shù)據(jù), 就可以繼續(xù)處理后邊的事情了.
簡版
整個(gè)簡單版本. 主線程維護(hù)一個(gè)所有連接的列表, 每次循環(huán)讀取所有列表, 有數(shù)據(jù)就處理, 沒有就跳過.
- 創(chuàng)建套接字
- 綁定端口號(hào)
- 開始監(jiān)聽
- 監(jiān)聽到連接, 將連接加到連接列表中, 循環(huán)讀取連接列表中的所有連接, 對(duì)有數(shù)據(jù)的進(jìn)行處理
畫個(gè)丑圖:
問題分析
現(xiàn)在這樣處理貌似是比開線程要好一些了, 但是事實(shí)是這樣么? 眾所周知, 其中的read操作是調(diào)用系統(tǒng)函數(shù), 簡單說就是要進(jìn)行進(jìn)程的切換, 從用戶進(jìn)程切換到系統(tǒng)進(jìn)程. 連接少還好, 如果有十萬個(gè)連接, 甚至更多呢? 每次循環(huán)都會(huì)頻繁的調(diào)用系統(tǒng)函數(shù), 可能十萬次調(diào)用, 甚至其中有數(shù)據(jù)的只有一次, 其余調(diào)用都白掉了. 這無疑降低了性能.
其實(shí)解決方法說起來也很容易想到. 問題出在系統(tǒng)調(diào)用上, 頻繁的系統(tǒng)調(diào)用導(dǎo)致了問題的出現(xiàn). 如果能夠一次性將需要查詢的所有數(shù)據(jù)都發(fā)給系統(tǒng), 讓系統(tǒng)進(jìn)行查詢, 那不就只需要一次切換就可以了么?.
select版本
為了解決上面的問題. 可以批量將待查詢的連接發(fā)給系統(tǒng). 出現(xiàn)了select, 這就是說的 多路復(fù)用 了. 在系統(tǒng) 中, 無論是監(jiān)聽端口還是建立了連接, 程序拿到的都是一個(gè)文件描述符, 將這些文件描述符批量查詢就是了.
直接上丑圖:
這里和上一個(gè)版本相比, 將循環(huán)檢查交到了系統(tǒng)去做, 只發(fā)生一次進(jìn)程的切換. select 簡單說, 就是你告訴系統(tǒng), 你需要哪些數(shù)據(jù), 你同幫你遍歷找找, 然后將結(jié)果返回給你.
問題分析
這樣確實(shí)要好上一些, 但是上面的問題還沒有完全解決. 比如有10萬個(gè)連接, 其中有數(shù)據(jù)的只有一個(gè), 那就回有9999次無效的操作, 雖然這些無效的操作是由系統(tǒng)做的, 但不一樣么, 系統(tǒng)做的無效操作, 你應(yīng)用程序也得等著啊. 而且每次查詢都要把所有的需要的都傳過去, 10萬個(gè)就要傳10萬了, 藍(lán)瘦.
問題出在哪呢? 需要循環(huán)遍歷, 是因?yàn)椴恢滥男┻B接是有數(shù)據(jù)的, 所以只能一個(gè)一個(gè)的看. 如果可以不 需要遍歷, 直接知道哪些連接是有數(shù)據(jù)的, 然后直接拿到數(shù)據(jù)返回就好了.
epoll
跟上一個(gè)版本相比, 現(xiàn)在不通過批量查詢的方式了, 而是通過回調(diào)的方式. 簡單說, 建立一個(gè)需要回調(diào)的連接, 將需要監(jiān)聽的文件描述符都扔給他, 當(dāng)有新數(shù)據(jù)到達(dá)時(shí), 會(huì)返回給你.
上丑圖:
看著是不是有點(diǎn)暈了? 其實(shí)它和select版本的區(qū)別簡單來說, epoll是將你需要監(jiān)聽的列表交給系統(tǒng)維護(hù), 這樣當(dāng)有新數(shù)據(jù)來的時(shí)候, 系統(tǒng)知道這是你要的, 等你下次來拿的時(shí)候, 直接給你了, 少去了上面的系統(tǒng)遍歷. 同時(shí), 也沒有select查詢時(shí)那一大堆參數(shù), 每次都只調(diào)用一次進(jìn)行綁定即可.
那系統(tǒng)是怎么知道新數(shù)據(jù)的到來呢? 這里靠的是事件中斷, 忘得差不多了, 回頭再看看.
epoll 簡單說就是, 你告訴系統(tǒng), 你需要哪些數(shù)據(jù), 然后等著, 有數(shù)據(jù)了系統(tǒng)就通知你, 然后你去讀.
以上select, epoll是兩種多路復(fù)用的技術(shù), 當(dāng)然, 還有多路復(fù)用還有其他的.
據(jù)說redis的多路復(fù)用對(duì)系統(tǒng)方法進(jìn)行了封裝, 不過我還沒看, 再議!!!