首頁 > 農業

【八斗之才】淺談網路IO

由 IDEADATA大資料 發表于 農業2021-07-01

簡介其中最複雜的引數型別struct aiocb亦是非常的精巧,整個結構體總共才7個成員,使用起來很方便,結構體成員如下:資料型別名稱描述Intaio_fildes檔案描述符off_taio_offset檔案偏移volatile void *a

外圍裝置的i o控制分為哪幾類

【八斗之才】淺談網路IO

雙十一剁手節,在享受剁手的快感後,阿里公佈了雙十一交易的峰值又破紀錄了,每秒25。6萬,這卓越的成績在全球恐怕也是沒有對手了。馬雲說辦雙十一不賺錢,目的有四個,一是給消費者帶來快樂,二是給商家帶來快樂,三是給阿里帶來技術的提升,四是給阿里帶來組織人才的提升。雖說語出驚人,但是第三個目的確實是實實在在的,25。6萬每秒的成績絕非能在一般的場景中鍛煉出來,而實現這樣超高水準成績涉及到硬體、軟體等方方面面的數百項技術,絕對是超級工程。而在這個超級工程中,最重要的基礎之一就是高併發的網路I/O操作,不論是客戶端到伺服器,還是伺服器叢集間通訊、伺服器內部資料互動,都少不了網路I/O操作,要做到高併發的業務,穩定可靠的高效能網路I/O併發是基石。

高併發是個相對的概念,作為人類,跟一個人講話會很輕鬆,同時跟兩個人講話勉勉強強,能同時跟5個人講話就很牛了,若能同時跟50個人講話,那就可以說這個人是高併發了。高併發是可以形容各種業務的,前面提到阿里的支付業務,每秒25。6萬筆是舉世矚目的成績,但是資料庫簡單的查詢業務其實可以輕輕鬆鬆達到百萬甚至千萬次每秒,故高併發是必須要和某一項具體業務綁在一起的,為了更加明確和清晰,剝離障眼的外衣,只看精華,只看不考慮任何應用業務的網路I/O併發。

網路I/O併發發展了幾十年,加上各種新興高階程式語言測出不窮,在網路I/O上做了各種最佳化或封裝,搗鼓來搗鼓去,發明了reactor模型、proactor模型等等,萬變不離其宗,無非都是四種網路I/O模型。這四種網路I/O模型都是TCP下的模型,適用於所有基於TCP的網路協議,基本覆蓋了網際網路絕大多數的網路通訊協議,而UDP等非流式網路協議不討論。

說了半天,先來認識一下四種模式的大名:同步阻塞、同步非阻塞、非同步阻塞、同步非阻塞。為了更好緊貼主旨,簡化邏輯,以下沒有說明的情況下只討論I/O的讀寫操作,異常和連線建立等操作略過。

同步阻塞

相信這是所有人剛接觸網路程式設計時最先學習就是同步阻塞模式,這種模式非常的簡單容易理解,在實際多應用於業務簡單的客戶端。

這種模式下的socket被設定為阻塞模式,所謂的阻塞就是socket在建立連線、讀、寫操作上都可能會被阻塞,直到socket被啟用喚醒才執行I/O操作;而所謂的同步指的是應用執行緒必須要等I/O操作結束,即使被阻塞了也要等。打個比方去ATM機取錢,只有輸入了密碼才能進行下一步,即使ATM反應很慢,輸了密碼還載入半天,這種互動模式雖然效率低,不過好在簡單和保險。

【八斗之才】淺談網路IO

同步非阻塞

同步非阻塞,顧名思義,socket被設定為非阻塞模式,但依舊工作在同步模式下。這種模式在實際中很少見,應用的地方不是很多,主要針對有大量I/O讀寫的業務,比如FTP服務,而大多數業務場景下同步非阻塞模式的價效比不高,較大的浪費機器資源。

在該模式下的應用執行緒並不知道作業系統核心是否準備好了socket上的操作,需要不斷的呼叫核心的系統呼叫api判斷返回值,遇到socket可操作時便執行操作。由此看見,應用執行緒的所有系統呼叫都必須得到反饋才能執行接下來的流程,同步操作的特產非常明顯。

【八斗之才】淺談網路IO

非同步阻塞

終於到非同步了,非同步可謂是讓網路I/O併發效能得到巨大的提升,非同步模型的出現直接改變了網路I/O程式設計的歷史,得益於非同步模式可以寫出併發非常高的模型,現在的高效能網路I/O併發基本都是用非同步模式,並且分為阻塞和非阻塞兩種。

這一節專門講非同步阻塞模式,先囉嗦下何為非同步,所謂非同步指的呼叫者不需要等被呼叫者立即返回呼叫結果,區別於同步下呼叫者必須等到被呼叫者返回結果。然後再說阻塞,非同步阻塞的阻塞不好理解,同步模式下的阻塞指的是socket阻塞,而非同步阻塞的socket也是非阻塞的,那為什還叫非同步阻塞呢?

在解答這個問題前,先來了解下非同步阻塞用到的系統呼叫,非同步阻塞的實現依賴I/O多路複用(multiplexing)或者稱為“事件驅動”,主要用到的API有Select、Epoll、Kqueque等。上一小節說過socket在非阻塞模式下,透過系統呼叫操作狀態為不可操作的socket時會立即得到返回,然而這時的返回狀態是失敗的,I/O多路複用的出現很好的解決了這種多餘的呼叫,應用執行緒把感興趣的socket交給系統核心監測,當所有的socket都沒有可操作的狀態時被阻塞,當有任意一個socket狀態為可操作時喚醒執行緒,執行緒就可以去處理狀態發生變化的socket了。

I/O多路複用不僅讓socket可以非同步操作了,還可以讓一個執行緒同時處理多個socket,為單程序單執行緒編寫高併發的網路I/O提供了可能。但I/O多路複用的特徵也就是讓應用層的執行緒阻塞在I/O多路複用的系統呼叫上了,故稱為非同步阻塞模式。

【八斗之才】淺談網路IO

非同步非阻塞

有很多人說非同步阻塞模式不是真的非同步,非同步非阻塞模式才是真正的非同步,說的大概是非同步阻塞模式下I/O讀寫非同步呼叫並沒有真的呼叫讀寫API,而是把socket加入到監測列表裡,等到socket狀態變為可讀寫時才真正呼叫讀寫API。從呼叫形式看確實像個“偽非同步”,不過啊,誰教非同步阻塞是哥哥,出來的早,佔先機,加上效能足夠強勁,現在大部分的高併發I/O仍舊用的非同步阻塞模式,比如時下熱門的網路元件,C編寫的Libevent、Libev和JAVA編寫的Netty等等都是實現了事件驅動的Reactor模型,即非同步阻塞模式。而“真非同步”非同步非阻塞模式的API出現的稍晚(Linux下2。6才出現——AIO,windows下到vista出現——IOCP),以至於現成的網路元件比較少,C下比較出名的就是ACE,而微軟直接在語言層面C#支援了非同步非阻塞。

透過下圖可以看到,應用執行緒直接透過系統的非同步API操作socket,不需要等待返回操作結果也不會阻塞,直接去幹別的事就好了,作業系統核心會負責socket上的一切工作,等待socket上的操作結束,根據應用執行緒給的引數作業系統會透過呼叫非同步執行緒告知應用層socket操作完畢以及處理接下來的邏輯,或者更簡單粗暴的直接透過訊號打斷應用執行緒,告知socket操作完畢及處理後續邏輯。

【八斗之才】淺談網路IO

看過非同步非阻塞模式是不是覺得直接秒殺前面三種模式,從理論上說,非同步非阻塞模式的確是效能最好的一種模式。

Linux的非同步API

AIO介紹

現在量大常見的作業系統都提供了成熟穩定的非同步API,windows下的IOCP,以及Linux下的AIO,當然非同步API既可以實現非同步阻塞模型又可以實現非同步非阻塞模型,比如windows下的很多語言實現的Reactor模型就是透過IOCP,但是要寫出效能極致的I/O高併發還是實現“真非同步”的非同步非阻塞。

Windows的IOCP就不用贅述了,來簡單認識不常見的Linux下的非同步API:AIO。AIO的API可以用短小精悍來形容,總共才7個介面,分別如下:

API函式

原型

說明

aio_read

int aio_read( struct aiocb *aiocbp );

請求非同步讀操作

aio_error

int aio_error( struct aiocb *aiocbp );

檢查非同步請求的狀態

aio_return

ssize_t aio_return( struct aiocb *aiocbp );

獲得完成的非同步請求的返回狀態

aio_write

int aio_write( struct aiocb *aiocbp );

請求非同步寫操作

aio_suspend

int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout );

掛起呼叫程序,直到一個或多個非同步請求已經完成(或失敗);這個API可以用來實現非同步阻塞

aio_cancel

int aio_cancel( int fd, struct aiocb *aiocbp );

取消非同步 I/O 請求

lio_listio

int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig );

發起一系列 I/O 操作

可以看到介面設計得相當巧妙,介面引數非常少,最多也才4個引數,資料型別也很簡單,除了struct aiocb都是常規資料型別。其中最複雜的引數型別struct aiocb亦是非常的精巧,整個結構體總共才7個成員,使用起來很方便,結構體成員如下:

資料型別

名稱

描述

Int

aio_fildes

檔案描述符

off_t

aio_offset

檔案偏移

volatile void *

aio_buf

使用者資料緩衝區指標

size_t

aio_nbytes

待傳輸資料長度

Int

aio_reqprio

請求優先順序

struct sigevent

aio_sigevent

非同步通知結構

Int

aio_lio_opcode

要執行的操作

我們已經熟悉了AIO的所有介面,對,你沒看錯,總共需要我們記住的才7個函式,僅僅就這7個函式,就可以完成不可思議的非同步I/O操作。而這僅有的7個函式,又可以劃分成3種類型:

I/O操作控制

aio_read、aio_write、lio_listio、aio_cancel

獲取I/O操作狀態

aio_error、aio_suspend

獲取I/O操作結果

aio_return

I/O操作控制型別中有三個介面aio_read、aio_write、lio_listio是發起I/O操的,一個介面aio_cancel用來取消操作。aio_read、aio_write不用解釋了,看字面就知道分別是發起讀寫操作,而lio_listio是發起批次操作,既可以是讀也可以是寫。

獲取I/O操作狀態型別中只有兩個介面,aio_error是立即獲取狀態,aio_suspend則會阻塞呼叫程序,直到I/O操作結束。

獲取I/O操作結果的介面就一個aio_return,只有在I/O操作狀態為結束的時候才能呼叫。

AIO的使用流程就更簡單了,AIO提供的API少而精悍,可以根據需要靈活的多種方式呼叫,為了深入淺出,先介紹兩種方式呼叫,輪詢的方式和通知的方式,如下圖所示,從圖上可以看出,輪詢的方式更為直接簡單。

【八斗之才】淺談網路IO

AIO的輪詢方式示例

輪詢方式非常簡單,發起一個I/O操作,然後系統核心就去非同步處理這個操作了,這時候主執行緒也不幹別的,迴圈獲取I/O操作的狀態,直到I/O操作的狀態為結束,最後獲取I/O操作的結果即可。沒錯,核心層的I/O操作確實是非同步的,只不過應用層執行緒在I/O操作期間自我等待同步,所以白搭,對於客戶端來說就是非非同步的。直接上程式碼:

程式碼清單1

int fd, ret;

struct aiocb my_aiocb;

//開啟一個檔案

fd = open( “file。txt”, O_RDONLY );

bzero( (char *)&my_aiocb, sizeof(struct aiocb) );

//給緩衝區分配空間

my_aiocb。aio_buf = malloc(BUFSIZE+1);

//引數賦值

my_aiocb。aio_fildes = fd;

my_aiocb。aio_nbytes = BUFSIZE;

my_aiocb。aio_offset = 0;

//發起非同步讀取操作

ret = aio_read( &my_aiocb );

//此處迴圈獲取操作狀態,相當於同步非阻塞了

while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;

//I/O操作結束,獲取結果

if ((ret = aio_return( &my_iocb )) > 0) {

//成功,獲取了讀取的位元組數

} else {

//操作失敗

}

AIO的通知方式示例

通知方式使用也非常簡單,發起一個I/O操作,這與輪詢方式的完全一致,區別在於狀態獲取,主執行緒發起操作後就去幹別的了,不再傻傻的等著獲取I/O操作狀態,這才是真的非同步嘛,你幹你的,我幹我的。那呼叫者怎麼知道,I/O操作結束了沒有呢,答案就是非同步通知,其實AIO的非同步通知並不是什麼新科技,乃是POSIX-sigevent,訊號事件,經典的非同步事件通知機制,熟悉的讀者看到前文struct sigevent這個結構體時估計就已經猜到用來幹啥的。核心在I/O操作結束的時候,透過訊號中斷主執行緒或者建立一個新的執行緒告知使用者I/O操作結束了,至於是用訊號中斷主執行緒還是建立新執行緒,這個由使用者控制。無論哪種方式通知,使用者在收到通知後,只需檢查I/O操作的狀態,當狀態值為已結束,獲取結果即可。

接下來給出一段使用訊號中斷作為一步通知程式碼樣例:

程式碼清單2

//發起I/O操作

void setup_io( 。。。 ){

int fd;

struct sigaction sig_act;//安裝訊號使用的結構

struct aiocb my_aiocb;

。。。

sigemptyset(&sig_act。sa_mask);

sig_act。sa_flags = SA_SIGINFO;//設定訊號可以傳參

sig_act。sa_sigaction = aio_completion_handler;//設定回撥函式指標

//常規引數賦值

bzero( (char *)&my_aiocb, sizeof(struct aiocb) );

my_aiocb。aio_fildes = fd;

my_aiocb。aio_buf = malloc(BUF_SIZE+1);

my_aiocb。aio_nbytes = BUF_SIZE;

my_aiocb。aio_offset = next_offset;

//訊號相關賦值

//事件發生時的行為,SIGEV_SIGNAL為傳送訊號,若是建立執行緒則是SIGEV_THREAD

my_aiocb。aio_sigevent。sigev_notify = SIGEV_SIGNAL;

//訊號的值,自定義,這裡給的SIGIO

my_aiocb。aio_sigevent。sigev_signo = SIGIO;

//設定訊號傳送時傳遞的引數指標

my_aiocb。aio_sigevent。sigev_value。sival_ptr = &my_aiocb;

//安裝訊號

ret = sigaction( SIGIO, &sig_act, NULL );

//發起非同步讀操作

ret = aio_read( &my_aiocb );

}

//訊號關聯的回撥函式

void aio_completion_handler( int signo, siginfo_t *info, void *context ){

//檢查訊號值

if (info->si_signo == SIGIO) {

struct aiocb *req = (struct aiocb *)info->si_value。sival_ptr;

//獲取I/O操作狀態

if (aio_error( req ) == 0) {

//若I/O操作結束,獲取結果

ret = aio_return( req );

}

}

結束語

曾經同步非同步、阻塞非阻塞困惑了我很長一段時間,透過認識四種I/O模式,才漸漸領悟兩種概念。

在討論網路I/O模型的時候,非同步、同步的時候需要從兩個層面理解。一是API層面,所謂同步API就是呼叫後必須等待操作完成,才能獲取結果,同步阻塞、同步非阻塞、非同步阻塞使用I/O操API都是同步API,而非同步則是呼叫後不要等待操作結束,而是透過回撥或通知的方式獲取操作結果,AIO、IOCP才提供了非同步API。另一個是通訊的層面,同步指的是A發給B的訊息後,若要接收B回覆的訊息只能透過等待的方式,在等待的過程中不能幹別的;而非同步通訊時,A發給B訊息後,不用管B什麼時候回覆訊息,而收到B回覆訊息的時候可以透過回撥或者通知及時處理。

阻塞非阻塞可以理解為呼叫API的時候會不會被阻塞,阻塞指請求操作時,因為埠繁忙或未準備好資料等原因導致不能操作的時候,會等待,直到等到可以操作的時候進行操作並返回結果;而非阻塞的請求會在不可操作的時候會立即獲取結果,而不用等待。

鑑於篇幅有限,以上只是每種網路I/O模式做了介紹,並未深入討論如何實現一個可用的高效能I/O網路模型。用於實際生產工程的網路框架緊靠這些遠遠不夠的,一個合適的模型只是一張面積材質剛剛好的白紙,還需要合適的記憶體管理模型,以及面對執行緒安全等等問題,才能編寫出真正可用的高效能的網路I/O框架。

【八斗之才原創集】

淺談Docker技術的實際應用

漫談雲計算

淺談廠商在銀行業大資料建設領域的建設能力

淺談銀行零售客戶細分與營銷

軟體設計開發經驗分享

網頁設計參考標準

簡化圖表,凸顯資料價值

談一談架構相關的那些事

淺談java效能最佳化

NBA球員場上位置的預測分析

微服務—軟體架構又一春

NodeJS作為Web架構中間層的使用

傳統數倉從 Teradata 遷移到 Hadoop平臺實踐

…… ……

更多原創請查閱www。ideadata。com。cn觀點與知識庫

關於IDEADATA

IDEADATA專注於從資料到資訊的有效管理與應用,是領先的商業資訊服務技術提供商,是資料倉庫及大資料技術和應用的先行實踐者。

Tags:非同步aio阻塞aiocb操作