關於 IO 和 NIO 的思考

I/O 的實際操作由內核執行,其中一個重要手段是緩衝區。簡單來說 I/O 可分爲兩類:面向磁盤和麪向網絡,Java 也是針對這兩者來抽象設計 API,相關的類主要在 java.iojava.nio 包中,簡稱爲 BIO 和 NIO。

爲什麼設計 NIO

一個直接原因就是爲了更好的利用操作系統特性,改善和擴展原有 API。與 NIO 相關的規範有兩個:

  • JSR 51:它是 NIO 的第一個規範,關注緩衝區、通道和字符集的設計,引入一個簡單的面向緩衝區的 I/O 模型,並且提供一套非阻塞、I/O 多路複用、可擴展的 API;
  • JSR 203(NIO.2):它在前者的基礎上,添加新的文件系統的抽象,完善現有 Socket 通道的配置,添加多播數據報的支持,並且定義了一個異步 I/O 編程 API。

那麼,傳統的 BIO 又有什麼弊端?NIO 又是如何改進的?

文件操作

關於 java.io.file,它的不足之處在於:

  • 當查詢文件屬性時,如修改時間或文件類型,都會發生系統調用,並且這些組合操作非常常見,造成性能問題;
  • 部分方法在發生錯誤時返回 false 而不是拋出異常,比如 delete、rename;
  • 一些 OS 常用功能不支持,比如符號鏈接、文件鎖定、內存映射等。

而 NIO 支持批量獲取文件屬性,對文件、目錄的處理也重新設計,提供 FileLock、MappedByteBuffer。

網絡通信

傳統 BIO 是阻塞式、基於流的I/O,其網絡服務器模型是一連接一線程,通常採用線程池優化,但一個進程或者計算機打開的線程數是有限的,可擴展性差。

NIO 是基於緩衝區(也是由字節流或字符流組成)的,對原始 I/O 提供了新的抽象 - Channel(通道)。Channel 表示一個到硬件設備、文件或網絡套接字的連接,與 java.net.Socket 的區別是:

  • 可配置非阻塞,允許事件驅動的設計,提供了一種更加可擴展的服務器開發;
  • 面向字節緩衝區,可實現 零拷貝 執行 I/O ,一端得是 FileChannel。

NIO 主要目標是設計、開發可擴展/可伸縮性服務器,讓少量的線程管理大量的客戶端連接,而靈活的代價是編程複雜,常用的框架是 Netty,這裏想到一個問題,Netty 宣稱的零拷貝與OS級別的有區別嗎?

服務器常用的優化手段是對象池、減少數據複製(內核到用戶進程或用戶進程內部)、減少上下文切換和鎖,ByteBuffer 本質就是提供了一個可複用的 byte[] 數組,而 BIO 做好這些優化也不見得比 NIO 慢,那麼如何選擇I/O模型?

I/O 模型的選擇

首先了解一下 C10k problem - 描述單機處理1萬個併發連接的問題,兩個不同的概念:

  • 併發連接(concurrent connections):在有限的時間內響應請求,關注高效的連接調度;
  • 每秒請求數(requests per second):快速處理請求以響應,關注高吞吐量。

C10k 問題的本質在於 CPU,即線程數,單機創建大量線程,不僅佔用大量的內存,頻繁的數據複製和上下文切換還會導致 OS 崩潰。

如果採用 BIO 一個直觀的解決辦法是水平擴展,採用分佈式系統,但如果併發量上升到百萬、千萬、甚至上億,那麼服務器的成本得多大?解決此問題的關鍵是減少線程數,提高單機的處理能力,而如何使用少量的線程管理大量的連接,則在操作系統層面解決了,也就是 Linux 下的 Epoll,Java 中的 NIO。如果你的應用面臨 C10k 問題,NIO 是最好的選擇。

那 BIO 有什麼用呢?大家都用線程池,你有 ByteBuffer,我也可以自己維護字節緩衝,照樣成塊讀取,阻塞無非因爲 I/O 延遲高,那換成 SSD和光纖,而且我編程簡單,唯一的缺點就是擴展性差了點。:)

至於如何選擇 I/O 模型,需要結合業務場景,綜合考慮:

  • 短連接還是長連接
  • 預計最大的併發數
  • 預計每個連接的數據量,即流量的大小

但感覺還是很難給出明確的答案,簡單來說,併發量低的可採用 BIO,高的可採用 NIO,至於 AIO 它應該不太成熟,不過多描述了。

小結

上文強調了 BIO 可擴展性差,那麼什麼是可擴展性?參考 wiki-Scalability 對它的描述,簡單概括如下:

Scalability 是系統,網絡或進程處理越來越多的工作的能力,或者它可以(水平或垂直)擴展以適應這種增長的潛力。例如,如果系統在添加資源(通常是硬件)時能夠在負載增加下增加其總輸出,則認爲該系統是可擴展的。

而 BIO 的性能和連接數與機器性能的關係往往是非線性的。

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