一惶桐、Linux 網(wǎng)絡(luò)I/O模型
Linux的內(nèi)核秉承一切皆文件的理念撮弧,普通文件、目錄姚糊、字符設(shè)備贿衍、塊設(shè)備和網(wǎng)絡(luò)設(shè)備(套接字)等在Unix/Linux都被當(dāng)做文件來對待。雖然他們的類型不同救恨,但是linux系統(tǒng)為它們提供了一套統(tǒng)一的操作接口贸辈。對一個文件的讀寫操作會調(diào)用內(nèi)核提供的系統(tǒng)命令,返回一個文件描述符(簡稱:fd)肠槽,而對于一個socket的讀寫也有響應(yīng)的描述符擎淤,簡稱socketfd奢啥,描述符就是一個數(shù)字,它指向內(nèi)核中的一個結(jié)構(gòu)體(文件路徑嘴拢,數(shù)據(jù)區(qū)等一些屬性)桩盲。
根據(jù)UNIX網(wǎng)絡(luò)編程對I/O模型的分類,UNIX提供了5種I/O模型炊汤,分別如下:
1. 阻塞I/O模型(Blocking IO):
最常用的I/O模型就是阻塞I/O模型正驻,默認(rèn)情況下,所有的文件操作都是阻塞的抢腐。應(yīng)用進程向內(nèi)核發(fā)起 I/O 請求姑曙,發(fā)起調(diào)用recvfrom的線程一直等待內(nèi)核返回結(jié)果。一次完整的 I/O 請求稱為BIO(Blocking IO迈倍,阻塞 I/O)伤靠,所以 BIO 在實現(xiàn)異步操作時,只能使用多線程模型啼染,一個請求對應(yīng)一個線程宴合。但是,線程的資源是有限且寶貴的迹鹅,創(chuàng)建過多的線程會增加線程切換的開銷卦洽。
2、非阻塞I/O模型(non-blocking IO):
recvfrom從應(yīng)用層到內(nèi)核斜棚,如果該緩沖區(qū)沒有數(shù)據(jù)的話阀蒂,直接返回一個EWOULDBLOCK錯誤,一般都對非阻塞I/O模型進行輪詢的時候就是檢查這個狀態(tài)弟蚀,看內(nèi)核是否有數(shù)據(jù)到來蚤霞。NIO 相比 BIO 雖然大幅提升了性能,但是輪詢過程中大量的系統(tǒng)調(diào)用導(dǎo)致上下文切換開銷很大义钉。所以昧绣,單獨使用非阻塞 I/O 時效率并不高,而且隨著并發(fā)量的提升捶闸,非阻塞 I/O 會存在嚴(yán)重的性能浪費夜畴。
3、多路復(fù)用I/O模型
多路復(fù)用實現(xiàn)了一個線程處理多個 fd的操作删壮。多路指的是多個數(shù)據(jù)通道斩启,復(fù)用指的是使用一個或多個固定線程來處理每一個 Socket。select/poll是順序掃描fd是否就緒醉锅,而且支持的fd數(shù)量有限兔簇。linux還提供了epoll的實現(xiàn)方式,基于事件驅(qū)動方式代替順序掃描,因此性能更高垄琐。當(dāng)有fd就緒時边酒,立即回調(diào)函數(shù)rollback。
select狸窘、poll墩朦、epoll 都是 I/O 多路復(fù)用的具體實現(xiàn),線程一次 select 調(diào)用可以獲取內(nèi)核態(tài)中多個數(shù)據(jù)通道的數(shù)據(jù)狀態(tài)翻擒。多路復(fù)用解決了同步阻塞 I/O 和同步非阻塞 I/O 的問題氓涣,是一種非常高效的 I/O 模型。
3.1陋气、epoll的特點
- 支持一個進程打開的scoket描述符(fd)不受限制(僅受限于操作系統(tǒng)的最大文件句柄數(shù))
- I/O效率不會隨著fd數(shù)目的增加而線性下降
- 使用mmap加速內(nèi)核與用戶空間的消息傳遞
- epoll的API更加簡單
4劳吠、信號驅(qū)動I/O模型
信號驅(qū)動 I/O 并不常用,它是一種半異步的 I/O 模型巩趁。在使用信號驅(qū)動 I/O 時痒玩,通過系統(tǒng)調(diào)用sigaction執(zhí)行一個信號處理函數(shù)。當(dāng)數(shù)據(jù)準(zhǔn)備就緒后议慰,內(nèi)核通過發(fā)送一個 SIGIO 信號通知應(yīng)用進程蠢古,應(yīng)用進程就可以開始讀取數(shù)據(jù)了。
5别凹、異步I/O模型
異步 I/O 最重要的一點是從內(nèi)核緩沖區(qū)拷貝數(shù)據(jù)到用戶態(tài)緩沖區(qū)的過程也是由系統(tǒng)異步完成草讶,應(yīng)用進程只需要在指定的數(shù)組中引用數(shù)據(jù)即可。異步 I/O 與信號驅(qū)動 I/O 這種半異步模式的主要區(qū)別:信號驅(qū)動 I/O 由內(nèi)核通知何時可以開始一個 I/O 操作炉菲,而異步 I/O 由內(nèi)核通知 I/O 操作何時已經(jīng)完成堕战。
二、JAVA的I/O模型
在JDK1.4時颁督,新增了java.nio包,提供了很多進行異步I/O開發(fā)的API和類庫浇雹,主要的類和接口如下:
- 進行異步I/O操作的緩沖區(qū)ByteBuffer等
- 進行異步I/O操作的管道Pipe
- 進行各種I/O操作(異步或同步)的Channel沉御,包括ServerSocketChannel和SocketChannel
- 多種字符集的編碼能力和解碼能力
- 實現(xiàn)非阻塞I/O操作的多路復(fù)用器Selector
- 基于流行的Perl實現(xiàn)的正則表達式類庫
- 文件通道FileChannel
雖然提供的NIO類庫極大的促進了JAVA的異步非阻塞變成的發(fā)展和應(yīng)用,但依然存在對文件系統(tǒng)的處理能力不足昭灵,主要有: - 沒有統(tǒng)一的文件屬性(例如讀寫權(quán)限)
- API能力比較弱吠裆,例如目錄的級聯(lián)創(chuàng)建和遞歸遍歷,往往需要自己實現(xiàn)
- 底層存儲系統(tǒng)的一些高級API無法使用
- 所有文件操作都是同步阻塞調(diào)用烂完,不支持異步文件讀寫操作
在JDK1.7中试疙,對NIO類庫進行了升級,被稱為NIO2.0它主要提供了三個方面的改進: - 提供能夠批量獲取文件屬性的API抠蚣,這些API具有平臺無關(guān)性祝旷,不與特性的文件系統(tǒng)相耦合。另外它還提供了標(biāo)準(zhǔn)文件系統(tǒng)的SPI,供各個服務(wù)提供商擴展實現(xiàn)
- 提供AIO功能怀跛,支持基于文件的異步I/O操作和針對網(wǎng)絡(luò)套接字的異步操作
- 完成JSR-51定義的通道功能距贷,包括對配置和多播數(shù)據(jù)報的支持等
三、Netty的I/O模型
Netty 的 I/O 模型是基于非阻塞 I/O 實現(xiàn)的吻谋,底層依賴的是 JDK NIO 框架的多路復(fù)用器 Selector忠蝗。一個多路復(fù)用器 Selector 可以同時輪詢多個 Channel,采用 epoll 模式后漓拾,只需要一個線程負(fù)責(zé) Selector 的輪詢阁最,就可以接入成千上萬的客戶端。
在 I/O 多路復(fù)用的場景下骇两,當(dāng)有數(shù)據(jù)處于就緒狀態(tài)后速种,需要一個事件分發(fā)器(Event Dispather),它負(fù)責(zé)將讀寫事件分發(fā)給對應(yīng)的讀寫事件處理器(Event Handler)脯颜。事件分發(fā)器有兩種設(shè)計模式:Reactor 和 Proactor哟旗,Reactor 采用同步 I/O, Proactor 采用異步 I/O栋操。
Reactor 實現(xiàn)相對簡單闸餐,適合處理耗時短的場景,對于耗時長的 I/O 操作容易造成阻塞矾芙。Proactor 性能更高舍沙,但是實現(xiàn)邏輯非常復(fù)雜,目前主流的事件驅(qū)動模型還是依賴 select 或 epoll 來實現(xiàn)剔宪。
(摘自 Lea D. Scalable IO in Java )
上圖所描述的便是 Netty 所采用的主從 Reactor 多線程模型拂铡,所有的 I/O 事件都注冊到一個 I/O 多路復(fù)用器上,當(dāng)有 I/O 事件準(zhǔn)備就緒后葱绒,I/O 多路復(fù)用器會將該 I/O 事件通過事件分發(fā)器分發(fā)到對應(yīng)的事件處理器中感帅。該線程模型避免了同步問題以及多線程切換帶來的資源開銷,真正做到高性能地淀、低延遲失球。
完美彌補 Java NIO 的缺陷
在 JDK 1.4 投入使用之前,只有 BIO 一種模式帮毁。開發(fā)過程相對簡單实苞。新來一個連接就會創(chuàng)建一個新的線程處理。隨著請求并發(fā)度的提升烈疚,BIO 很快遇到了性能瓶頸黔牵。JDK 1.4 以后開始引入了 NIO 技術(shù),支持 select 和 poll爷肝;JDK 1.5 支持了 epoll猾浦;JDK 1.7 發(fā)布了 NIO2陆错,支持 AIO 模型。Java 在網(wǎng)絡(luò)領(lǐng)域取得了長足的進步跃巡。
Netty 相比 JDK NIO 突出的優(yōu)勢:
易用性危号。 我們使用 JDK NIO 編程需要了解很多復(fù)雜的概念,比如 Channels素邪、Selectors外莲、Sockets、Buffers 等兔朦,編碼復(fù)雜程度非常高偷线。相反,Netty 在 NIO 基礎(chǔ)上進行了更高層次的封裝沽甥,屏蔽了 NIO 的復(fù)雜性声邦;Netty 封裝了更加人性化的 API,統(tǒng)一的 API(阻塞/非阻塞) 大大降低了開發(fā)者的上手難度摆舟;與此同時亥曹,Netty 提供了很多開箱即用的工具,例如常用的行解碼器恨诱、長度域解碼器等媳瞪,而這些在 JDK NIO 中都需要自己實現(xiàn)。
穩(wěn)定性照宝。 Netty 更加可靠穩(wěn)定蛇受,修復(fù)和完善了 JDK NIO 較多已知問題,例如臭名昭著的 select 空轉(zhuǎn)導(dǎo)致 CPU 消耗 100%厕鹃,TCP 斷線重連兢仰,keep-alive 檢測等問題。
可擴展性剂碴。 Netty 的可擴展性在很多地方都有體現(xiàn)把将,這里主要列舉其中的兩點:一個是可定制化的線程模型,用戶可以通過啟動的配置參數(shù)選擇 Reactor 線程模型忆矛;另一個是可擴展的事件驅(qū)動模型察蹲,將框架層和業(yè)務(wù)層的關(guān)注點分離。大部分情況下洪碳,開發(fā)者只需要關(guān)注 ChannelHandler 的業(yè)務(wù)邏輯實現(xiàn)递览。
更低的資源消耗
作為網(wǎng)絡(luò)通信框架叼屠,需要處理海量的網(wǎng)絡(luò)數(shù)據(jù)瞳腌,那么必然面臨有大量的網(wǎng)絡(luò)對象需要創(chuàng)建和銷毀的問題,對于 JVM GC 并不友好镜雨。為了降低 JVM 垃圾回收的壓力嫂侍,Netty 主要采用了兩種優(yōu)化手段:
對象池復(fù)用技術(shù)。 Netty 通過復(fù)用對象,避免頻繁創(chuàng)建和銷毀帶來的開銷挑宠。
零拷貝技術(shù)菲盾。 除了操作系統(tǒng)級別的零拷貝技術(shù)外,Netty 提供了更多面向用戶態(tài)的零拷貝技術(shù)各淀,例如 Netty 在 I/O 讀寫時直接使用 DirectBuffer懒鉴,從而避免了數(shù)據(jù)在堆內(nèi)存和堆外內(nèi)存之間的拷貝。