I/O模型


目前我們網絡所面臨的依然是高併發的問題,就像某cat11時的情況,瞬間的併發量是驚人的,當然我們會有很多種方法去解決這個問題,本文我們談論的是單臺服務器,如何提高自己對併發請求的處理能力。要想解決這個問題,我們需要先理清楚Unix和類Unix系統的I/O模型。

IO也就是輸入輸出即讀寫操作,在操作系統內部邏輯上一般會分兩個空間(實際是內存映射):用戶空間和內核空間。爲了保證數據的安全性,只有內核纔有權限從物理存儲(或網絡數據)上直接進行讀寫操作,而我們的服務進程都是處於用戶空間的,所以說一次IO操作就被分爲了兩個階段:1、內核從磁盤上讀取數據到內核空間;2、複製內核空間中的數據到用戶空間。瞭解這個之後我們就可以講解下5種不同的IO模型。

阻塞IOBlocking I/O

阻塞IO是最早期也是原理最簡單的I/O模式,應用進程發出一個I/O請求,該I/O操作不能立刻完成,它需要等待內核將數據讀取出來,再等待從內核空間中將數據拷貝出來,這兩個環節進行完之前,應用進程都處於阻塞狀態。

非阻塞IONon-blocking I/O

應用進程想內核發送一個I/O請求,如果沒有可用數據,內核會向用戶返回一個錯誤值,用戶會在將來的一個合適的時候再次進行請求操作,這樣就避免了進程的阻塞,這種往返的操作也被稱爲輪詢。這裏有一點需要注意的是,在Copy data from kernel user的時候進程依然是被阻塞的,之所以叫做非阻塞是因爲只有在內核讀寫數據的階段(上文的第一階段)才叫做IO操作,所以在需要有大量進程併發請求時非阻塞IO模式並不比阻塞IO模式效率高,甚至更加浪費資源。

多路複用IOI/O Multiplexing

多路複用IOLinux系統中一般只用於網絡IO,非阻塞IO中的用戶進程輪訓會消耗大量的系統資源,多路複用的提出就是將這種輪訓方式通過selectpoll系統調用來完成,使用select/poll 操作,進程可在多個socket 上等待網絡事件,當其中某個socket 發生某個網絡事件時,用戶可通過查看網絡事件對該socket 進行I/O 操作。但當所有socket 都沒有網絡事件發生時,進程還會阻塞起來。所以,多路複用I/O 模型本質上還是基於阻塞I/O 的。

信號驅動式IOSingle-driven I/O

信號驅動式IO也有叫事件驅動式IO,這種IO具有以上三種IO都不具有的優勢,即當用戶進程發起IO請求後即可離開,當內核將數據準備好後,將想用戶進程發送一個信號以通知其來拷貝數據,第一階段用戶進程時完全的非阻塞的也不需要進行輪訓,只有在第二階段用戶進程纔是阻塞狀態。但在多進程服務器中,信號驅動I/O 存在一個問題,即信號在產生和傳遞到目的進程之間,狀態標誌可能發生改變。

異步IOAsynchronous I/O

用戶進程提出IO請求後即可做其他的操作,當數據準備好之後,內核會直接通知用戶IO操作的結果。這種機制容易和信號驅動I/O混淆,兩者的區別是:異步I/O 返回時,I/O 操作已經完成,返回的是I/O 操作的結果;信號驅動I/O 只是通知進程可以開始進行I/O 操作,進程得到這個信號後纔開始I/O 操作。

五種不同IO模型的比較:

阻塞模型較爲直觀,服務器端爲每個請求的客戶連接建立一個線程或者進程,並處理這個連接數據。若有大量客戶請求連接,系統就建立大量線程或者進程,線程間的上下文切換使系統性能大受影響。這種模型適合併發數不多或服務器負載不大的情況。

非阻塞I/O 主要應用在單進程服務器中。若服務器希望在當前請求的I/O 未完成時去處理其他事務,就要選擇這種模型,而該模型並不知道何時再次進行I/O 請求操作,雖然可通過輪詢的方法查詢連接狀態,但輪詢操作會造成CPU 的浪費。

多路複用針對上述問題提出一種解決辦法,進程可在多個描述符上等待網絡I/O 事件(可讀、可寫),擔當沒有任何網絡事件發生時,進程會進入阻塞狀態。因此,該模型適合多併發連接的情況,且這些併發連接大多要處於活躍狀態。

信號驅動I/O的本質屬於異步I/O,但這種模型存在缺點,即傳統的UNIX/Linux 信號是不排隊的,對於某個進程,當一個信號處於掛起狀態時,若同一個信號再次產生則會被丟棄。這就不能保證每個事件的SIGIO 信號都被遞送到進程。所以,當有SIGIO 到達時進程並不能確定有多少事件發生了,而要檢查所有描述符;另外,SIGIO 只告訴進程有I/O 事件發生,並沒有關於事件是發生在哪些描述符上的信息,所以,檢查也是必須的。這種檢查通常是調用select/poll 來完成的,因此,一般服務器端很少使用這種模型

Linux C 函數庫形式提供AIO 操作函數,而C 庫通過建立用戶級線程實現AIO。如果應用程序請求大量異步I/O,就會產生大量線程,這些線程間的切換會導致系統性能下降。因此,AIO 適合併發數不多的服務器。

epoll 模型(poll的增強型)針對的是服務器高併發的情況。它的I/O 效率不會隨連接數的增加而下降,對一個數目較大的socket 集合來說,並不一定每個socket 在任何時候都處於活躍狀態,可能只有部分socket 處於活躍狀態,epoll 模型並不像傳統select/poll 的輪詢方法那樣掃描整個socket 集合,epoll 只給用戶返回活躍的socket。另外,epoll 利用mmap 讓內核和用戶空間共用一塊內存,省去了內核和用戶空間間數據的拷貝,提高系統效率。這種模型適用於高併發連接服務器且活躍的連接不是很高的情況。若大部分連接都不處於活躍狀態,那麼這種模型的效率不一定比select/poll 高。

從上述的IO模型中我們發現在現如今常見的大量活躍併發的場景中,只有epoll模型的效率是最高的。epoll 模型通過callback 方法實現系統異步通知,socket集合中活躍的socket 通過調用callback 函數通知客戶程序,省去了輪詢時間,提高了網絡性能,並且利用mmap 讓內核和用戶空間共用一塊內存,省去了內核和用戶空間間數據的拷貝,提高了系統效率。

PS

用通俗的一個例子來簡單描述下這幾個網絡IO模型:

一個釣魚的過程,魚釣上來放到魚桶裏是第一個階段(魚竿自動完成),將魚拿到自己家的廚房是第二個幾段。

阻塞IO:我們拋下魚竿,魚上鉤放桶裏再拿到廚房這個過程我們一隻在這裏等着,直到魚到廚房了我們再進行其他操作。

非阻塞IO:我們拋下魚竿,就去泡MM了,但是我們不專心泡MM,不時的回來看魚釣上來沒有,釣上來了我們就從魚桶裏的魚拿到自己的廚房裏。

多路複用IO:我們找了一個代理人,他可以一次拋下多個魚竿,我們就看着他,一有魚上來我們就拿走,如果所有魚竿都沒釣上來魚,我們就等着。

信號驅動式IO:我們拋下魚竿,然後就去泡MM了,等魚釣上來魚竿會給我們發短信,我們回來將桶裏的魚拿回家就行了。

異步IO:我們告訴漁場老闆我們要魚,然後就去泡MM了,等魚釣上來後老闆會直接快遞到我們家。

Epoll模型:我們告訴代理人要魚就去泡MM了,等魚釣上來,代理人會給我們發送信息,這裏的快捷地方是魚桶就等於我們的廚房。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章