今天我會對 NIO 進行一些補充,在[專欄第 11 講](http://time.geekbang.org/column/article/8369)中,我們初步接觸了 Java 提供的幾種 IO 機制,作為語言基礎類庫,Java 自身的 NIO 設計更偏底層,這本無可厚非,但是對于一線的應用開發者,其復雜性、擴展性等方面,就存在一定的局限了。在基礎 NIO 之上,Netty 構建了更加易用、高性能的網絡框架,廣泛應用于互聯網、游戲、電信等各種領域。
今天我要問你的問題是,對比 Java 標準 NIO 類庫,你知道 Netty 是如何實現更高性能的嗎?
## 典型回答
單獨從性能角度,Netty 在基礎的 NIO 等類庫之上進行了很多改進,例如:
* 更加優雅的 Reactor 模式實現、靈活的線程模型、利用 EventLoop 等創新性的機制,可以非常高效地管理成百上千的 Channel。
* 充分利用了 Java 的 Zero-Copy 機制,并且從多種角度,“斤斤計較”般的降低內存分配和回收的開銷。例如,使用池化的 Direct Buffer 等技術,在提高 IO 性能的同時,減少了對象的創建和銷毀;利用反射等技術直接操縱 SelectionKey,使用數組而不是 Java 容器等。
* 使用更多本地代碼。例如,直接利用 JNI 調用 Open SSL 等方式,獲得比 Java 內建 SSL 引擎更好的性能。
* 在通信協議、序列化等其他角度的優化。
總的來說,Netty 并沒有 Java 核心類庫那些強烈的通用性、跨平臺等各種負擔,針對性能等特定目標以及 Linux 等特定環境,采取了一些極致的優化手段。
## 考點分析
這是一個比較開放的問題,我給出的回答是個概要性的舉例說明。面試官很可能利用這種開放問題作為引子,針對你回答的一個或者多個點,深入探討你在不同層次上的理解程度。
在面試準備中,兼顧整體性的同時,不要忘記選定個別重點進行深入理解掌握,最好是進行源碼層面的深入閱讀和實驗。如果你希望了解更多從性能角度 Netty 在編碼層面的手段,可以參考 Norman 在 Devoxx 上的[分享](https://speakerdeck.com/normanmaurer/writing-highly-performant-network-frameworks-on-the-jvm-a-love-hate-relationship),其中的很多技巧對于實現極致性能的 API 有一定借鑒意義,但在一般的業務開發中要謹慎采用。
雖然提到 Netty,人們會自然地想到高性能,但是 Netty 本身的優勢不僅僅只有這一個方面,
下面我會側重兩個方面:
* 對 Netty 進行整體介紹,幫你了解其基本組成。
* 從一個簡單的例子開始,對比在[第 11 講](http://time.geekbang.org/column/article/8369)中基于 IO、NIO 等標準 API 的實例,分析它的技術要點,給你提供一個進一步深入學習的思路。
## 知識擴展
首先,我們從整體了解一下 Netty。按照官方定義,它是一個異步的、基于事件 Client/Server 的網絡框架,目標是提供一種簡單、快速構建網絡應用的方式,同時保證高吞吐量、低延時、高可靠性。
從設計思路和目的上,Netty 與 Java 自身的 NIO 框架相比有哪些不同呢?
我們知道 Java 的標準類庫,由于其基礎性、通用性的定位,往往過于關注技術模型上的抽象,而不是從一線應用開發者的角度去思考。我曾提到過,引入并發包的一個重要原因就是,應用開發者使用 Thread API 比較痛苦,需要操心的不僅僅是業務邏輯,而且還要自己負責將其映射到 Thread 模型上。Java NIO 的設計也有類似的特點,開發者需要深入掌握線程、IO、網絡等相關概念,學習路徑很長,很容易導致代碼復雜、晦澀,即使是有經驗的工程師,也難以快速地寫出高可靠性的實現。
Netty 的設計強調了 “**Separation Of Concerns**”,通過精巧設計的事件機制,將業務邏輯和無關技術邏輯進行隔離,并通過各種方便的抽象,一定程度上填補了了基礎平臺和業務開發之間的鴻溝,更有利于在應用開發中普及業界的最佳實踐。
另外,**Netty > java.nio + java. net!**
從 API 能力范圍來看,Netty 完全是 Java NIO 框架的一個大大的超集,你可以參考 Netty 官方的模塊劃分。

除了核心的事件機制等,Netty 還額外提供了很多功能,例如:
* 從網絡協議的角度,Netty 除了支持傳輸層的 UDP、TCP、[SCTP](https://en.wikipedia.org/wiki/Stream_Control_Transmission_Protocol)協議,也支持 HTTP(s)、WebSocket 等多種應用層協議,它并不是單一協議的 API。
* 在應用中,需要將數據從 Java 對象轉換成為各種應用協議的數據格式,或者進行反向的轉換,Netty 為此提供了一系列擴展的編解碼框架,與應用開發場景無縫銜接,并且性能良好。
* 它擴展了 Java NIO Buffer,提供了自己的 ByteBuf 實現,并且深度支持 Direct Buffer 等技術,甚至 hack 了 Java 內部對 Direct Buffer 的分配和銷毀等。同時,Netty 也提供了更加完善的 Scatter/Gather 機制實現。
可以看到,Netty 的能力范圍大大超過了 Java 核心類庫中的 NIO 等 API,可以說它是一個從應用視角出發的產物。
當然,對于基礎 API 設計,Netty 也有自己獨到的見解,未來 Java NIO API 也可能據此進行一定的改進,如果你有興趣可以參考[JDK-8187540](https://bugs.openjdk.java.net/browse/JDK-8187540)。
接下來,我們一起來看一個入門的代碼實例,看看 Netty 應用到底是什么樣子。
與[第 11 講](http://time.geekbang.org/column/article/8369)類似,同樣是以簡化的 Echo Server 為例,下圖是 Netty 官方提供的 Server 部分,完整用例請點擊[鏈接](http://netty.io/4.1/xref/io/netty/example/echo/package-summary.html)。

上面的例子,雖然代碼很短,但已經足夠體現出 Netty 的幾個核心概念,請注意我用紅框標記出的部分:
* [ServerBootstrap](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java),服務器端程序的入口,這是 Netty 為簡化網絡程序配置和關閉等生命周期管理,所引入的 Bootstrapping 機制。我們通常要做的創建 Channel、綁定端口、注冊 Handler 等,都可以通過這個統一的入口,以**Fluent**API 等形式完成,相對簡化了 API 使用。與之相對應,[Bootstrap](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/bootstrap/Bootstrap.java)則是 Client 端的通常入口。
* [Channel](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/Channel.java),作為一個基于 NIO 的擴展框架,Channel 和 Selector 等概念仍然是 Netty 的基礎組件,但是針對應用開發具體需求,提供了相對易用的抽象。
* [EventLoop](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/EventLoop.java),這是 Netty 處理事件的核心機制。例子中使用了 EventLoopGroup。我們在 NIO 中通常要做的幾件事情,如注冊感興趣的事件、調度相應的 Handler 等,都是 EventLoop 負責。
* [ChannelFuture](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/ChannelFuture.java),這是 Netty 實現異步 IO 的基礎之一,保證了同一個 Channel 操作的調用順序。Netty 擴展了 Java 標準的 Future,提供了針對自己場景的特有[Future](https://github.com/netty/netty/blob/eb7f751ba519cbcab47d640cd18757f09d077b55/common/src/main/java/io/netty/util/concurrent/Future.java)定義。
* ChannelHandler,這是應用開發者**放置業務邏輯的主要地方**,也是我上面提到的“Separation Of Concerns”原則的體現。
* [ChannelPipeline](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/ChannelPipeline.java),它是 ChannelHandler 鏈條的容器,每個 Channel 在創建后,自動被分配一個 ChannelPipeline。在上面的示例中,我們通過 ServerBootstrap 注冊了 ChannelInitializer,并且實現了 initChannel 方法,而在該方法中則承擔了向 ChannelPipleline 安裝其他 Handler 的任務。
你可以參考下面的簡化示意圖,忽略 Inbound/OutBound Handler 的細節,理解這幾個基本單元之間的操作流程和對應關系。

對比 Java 標準 NIO 的代碼,Netty 提供的相對高層次的封裝,減少了對 Selector 等細節的操縱,而 EventLoop、Pipeline 等機制則簡化了編程模型,開發者不用擔心并發等問題,在一定程度上簡化了應用代碼的開發。最難能可貴的是,這一切并沒有以可靠性、可擴展性為代價,反而將其大幅度提高。
我在[專欄周末福利](http://time.geekbang.org/column/article/12188)中已經推薦了 Norman Maurer 等編寫的《Netty 實戰》(Netty In Action),如果你想系統學習 Netty,它會是個很好的入門參考。針對 Netty 的一些實現原理,很可能成為面試中的考點,例如:
* Reactor 模式和 Netty 線程模型。
* Pipelining、EventLoop 等部分的設計實現細節。
* Netty 的內存管理機制、[引用計數](http://netty.io/wiki/reference-counted-objects.html)等特別手段。
* 有的時候面試官也喜歡對比 Java 標準 NIO API,例如,你是否知道 Java NIO 早期版本中的 Epoll[空轉問題](http://www.10tiao.com/html/308/201602/401718035/1.html),以及 Netty 的解決方式等。
對于這些知識點,公開的深入解讀已經有很多了,在學習時希望你不要一開始就被復雜的細節弄暈,可以結合實例,逐步、有針對性的進行學習。我的一個建議是,可以試著畫出相應的示意圖,非常有助于理解并能清晰闡述自己的看法。
今天,從 Netty 性能的問題開始,我概要地介紹了 Netty 框架,并且以 Echo Server 為例,對比了 Netty 和 Java NIO 在設計上的不同。但這些都僅僅是冰山的一角,全面掌握還需要下非常多的功夫。
## 一課一練
關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,Netty 的線程模型是什么樣的?
- 前言
- 開篇詞
- 開篇詞 -以面試題為切入點,有效提升你的Java內功
- 模塊一 Java基礎
- 第1講 談談你對Java平臺的理解?
- 第2講 Exception和Error有什么區別?
- 第3講 談談final、finally、 finalize有什么不同?
- 第4講 強引用、軟引用、弱引用、幻象引用有什么區別?
- 第5講 String、StringBuffer、StringBuilder有什么區別?
- 第6講 動態代理是基于什么原理?
- 第7講 int和Integer有什么區別?
- 第8講 對比Vector、ArrayList、LinkedList有何區別?
- 第9講 對比Hashtable、HashMap、TreeMap有什么不同?
- 第10講 如何保證集合是線程安全的? ConcurrentHashMap如何實現高效地線程安全?
- 第11講 Java提供了哪些IO方式? NIO如何實現多路復用?
- 第12講 Java有幾種文件拷貝方式?哪一種最高效?
- 第13講 談談接口和抽象類有什么區別?
- 第14講 談談你知道的設計模式?
- 模塊二 Java進階
- 第15講 synchronized和ReentrantLock有什么區別呢?
- 第16講 synchronized底層如何實現?什么是鎖的升級、降級?
- 第17講 一個線程兩次調用start()方法會出現什么情況?
- 第18講 什么情況下Java程序會產生死鎖?如何定位、修復?
- 第19講 Java并發包提供了哪些并發工具類?
- 第20講 并發包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么區別?
- 第21講 Java并發類庫提供的線程池有哪幾種? 分別有什么特點?
- 第22講 AtomicInteger底層實現原理是什么?如何在自己的產品代碼中應用CAS操作?
- 第23講 請介紹類加載過程,什么是雙親委派模型?
- 第24講 有哪些方法可以在運行時動態生成一個Java類?
- 第25講 談談JVM內存區域的劃分,哪些區域可能發生OutOfMemoryError?
- 第26講 如何監控和診斷JVM堆內和堆外內存使用?
- 第27講 Java常見的垃圾收集器有哪些?
- 第28講 談談你的GC調優思路?
- 第29講 Java內存模型中的happen-before是什么?
- 第30講 Java程序運行在Docker等容器環境有哪些新問題?
- 模塊三 Java安全基礎
- 第31講 你了解Java應用開發中的注入攻擊嗎?
- 第32講 如何寫出安全的Java代碼?
- 模塊四 Java性能基礎
- 第33講 后臺服務出現明顯“變慢”,談談你的診斷思路?
- 第34講 有人說“Lambda能讓Java程序慢30倍”,你怎么看?
- 第35講 JVM優化Java代碼時都做了什么?
- 模塊五 Java應用開發擴展
- 第36講 談談MySQL支持的事務隔離級別,以及悲觀鎖和樂觀鎖的原理和應用場景?
- 第37講 談談Spring Bean的生命周期和作用域?
- 第38講 對比Java標準NIO類庫,你知道Netty是如何實現更高性能的嗎?
- 第39講 談談常用的分布式ID的設計方案?Snowflake是否受冬令時切換影響?
- 周末福利
- 周末福利 談談我對Java學習和面試的看法
- 周末福利 一份Java工程師必讀書單
- 結束語
- 結束語 技術沒有終點
- 結課測試 Java核心技術的這些知識,你真的掌握了嗎?