天天看點

Netty In Action中文版 - 第一章:Netty介紹Netty In Action中文版 - 第一章:Netty介紹

Netty介紹

為什麼要使用non-blocking IO(NIO)

阻塞IO(blocking IO)和非阻塞IO(non-blocking IO)對比

Java NIO的問題和在Netty中的解決方案

Netty是基于Java NIO的網絡應用架構,如果你是Java網絡方面的新手,那麼本章将是你學習Java網絡應用的開始;對于有經驗的開發者來說,學習本章内容也是很好的複習。如果你熟悉NIO和NIO2,你可以随時跳過本章直接從第二章開始學習。在你的機器上運作第二章編寫的Netty伺服器和用戶端。

Netty是一個NIO client-server(用戶端伺服器)架構,使用Netty可以快速開發網絡應用,例如伺服器和用戶端協定。Netty提供了一種新的方式來使開發網絡應用程式,這種新的方式使得它很容易使用和有很強的擴充性。Netty的内部實作時很複雜的,但是Netty提供了簡單易用的api從網絡處理代碼中解耦業務邏輯。Netty是完全基于NIO實作的,是以整個Netty都是異步的。

網絡應用程式通常需要有較高的可擴充性,無論是Netty還是其他的基于Java NIO的架構,都會提供可擴充性的解決方案。Netty中一個關鍵組成部分是它的異步特性,本章将讨論同步(阻塞)和異步(非阻塞)的IO來說明為什麼使用異步代碼來解決擴充性問題以及如何使用異步。

對于那些初學網絡變成的讀者,本章将幫助您對網絡應用的了解,以及Netty是如何實作他們的。它說明了如何使用基本的Java網絡API,探讨Java網絡API的優點和缺點并闡述Netty是如何解決Java中的問題的,比如Eploo錯誤或記憶體洩露問題。

在本章的結尾,你會明白什麼是Netty以及Netty提供了什麼,你會了解Java NIO和異步處理機制,并通過本書的其他章節加強了解。

        David John Wheeler說過“在計算機科學中的所有問題都可以通過間接的方法解決。”作為一個NIO client-server架構,Netty提供了這樣的一個間接的解決方法。Netty提供了高層次的抽象來簡化TCP和UDP伺服器的程式設計,但是你仍然可以使用底層地API。

        (David John Wheeler有一句名言“計算機科學中的任何問題都可以通過加上一層邏輯層來解決”,這個原則在計算機各技術領域被廣泛應用)

        Netty的“quick and easy(高性能和簡單易用)”并不意味着編寫的程式的性能和可維護性會受到影響。從Netty中實作的協定如FTP,SMTP,HTTP,WebSocket,SPDY以及各種二進制和基于文本的傳統協定中獲得的經驗導緻Netty的創始人要非常小心它的設計。Netty成功的提供了易于開發,高性能和高穩定性,以及較強的擴充性。

        高調的公司和開源項目有RedHat, Twitter, Infinispan, and HornetQ, Vert.x, Finagle, Akka, Apache Cassandra, Elasticsearch,以及其他人的使用有助于Netty的發展,Netty的一些特性也是這些項目的需要所緻。多年來,Netty變的更廣為人知,它是Java網絡的首選架構,在一些開源或非開源的項目中可以展現。并且,Netty在2011年獲得Duke's Choice Award(Duke's Choice獎)。

        此外,在2011年,Netty的創始人Trustion Lee離開RedHat後加入Twitter,在這一點上,Netty項目獎會成為一個獨立的項目組織。RedHat和Twitter都使用Netty,是以它毫不奇怪。在撰寫本書時RedHat和Twitter這兩家公司是最大的貢獻者。使用Netty的項目越來越多,Netty的使用者群體和項目以及Netty社群都是非常活躍的。

        通過本書可以學習Netty豐富的功能。下圖是Netty架構的組成

Netty In Action中文版 - 第一章:Netty介紹Netty In Action中文版 - 第一章:Netty介紹

        Netty除了提供傳輸和協定,在其他各領域都有發展。Netty為開發者提供了一套完整的工具,看下面表格:

Development Area

Netty Features

Design(設計)

各種傳輸類型,阻塞和非阻塞套接字統一的API

使用靈活

簡單但功能強大的線程模型

無連接配接的DatagramSocket支援

鍊邏輯,易于重用

Ease of Use(易于使用)

提供大量的文檔和例子

除了依賴jdk1.6+,沒有額外的依賴關系。某些功能依賴jdk1.7+,其他特性可能有相關依賴,但都是可選的。

Performance(性能)

比Java APIS更好的吞吐量和更低的延遲

因為線程池和重用所有消耗較少的資源

盡量減少不必要的記憶體拷貝

Robustness(魯棒性)

魯棒性,可以了解為健壯性

連結快或慢或超載不會導緻更多的OutOfMemoryError

在高速的網絡程式中不會有不公平的read/write

Security(安全性)

完整的SSL/TLS和StartTLS支援

可以在如Applet或OSGI這些受限制的環境中運作

Community(社群)

版本釋出頻繁

社群活躍

除了列出的功能外,Netty為Java NIO中的bug和限制也提供了解決方案。我們需要深刻了解Netty的功能以及它的異步處理機制和它的架構。NIO和Netty都大量使用了異步代碼,并且封裝的很好,我們無需了解底層的事件選擇機制。下面我們來看看為什麼需要異步APIS。

        整個Netty的API都是異步的,異步處理不是一個新的機制,這個機制出來已經有一些時間了。對網絡應用來說,IO一般是性能的瓶頸,使用異步IO可以較大程度上提高程式性能,因為異步變的越來越重要。但是它是如何工作的呢?以及有哪些不同的模式可用呢?

        異步處理提倡更有效的使用資源,它允許你建立一個任務,當有事件發生時将獲得通知并等待事件完成。這樣就不會阻塞,不管事件完成與否都會及時傳回,資源使用率更高,程式可以利用剩餘的資源做一些其他的事情。

        本節将說明一起工作或實作異步API的兩個最常用的方法,并讨論這些技術之間的差異。

        回調一般是異步處理的一種技術。一個回調是被傳遞到并且執行完該方法。你可能認為這種模式來自JavaScript,在Javascript中,回調是它的核心。下面的代碼顯示了如何使用這種技術來擷取資料。下面代碼是一個簡單的回調

<b>[java]</b> view plain copy

package netty.in.action;

public class Worker {

    public void doWork() {

        Fetcher fetcher = new MyFetcher(new Data(1, 0));

        fetcher.fetchData(new FetcherCallback() {

            @Override

            public void onError(Throwable cause) {

                System.out.println("An error accour: " + cause.getMessage());

            }

            public void onData(Data data) {

                System.out.println("Data received: " + data);

        });

    }

    public static void main(String[] args) {

        Worker w = new Worker();

        w.doWork();

}

public interface Fetcher {

    void fetchData(FetcherCallback callback);

public class MyFetcher implements Fetcher {

    final Data data;

    public MyFetcher(Data data){

        this.data = data;

    @Override

    public void fetchData(FetcherCallback callback) {

        try {

            callback.onData(data);

        } catch (Exception e) {

            callback.onError(e);

        }

public interface FetcherCallback {

    void onData(Data data) throws Exception;

    void onError(Throwable cause);

public class Data {

    private int n;

    private int m;

    public Data(int n,int m){

        this.n = n;

        this.m = m;

    public String toString() {

        int r = n/m;

        return n + "/" + m +" = " + r;

上面的例子隻是一個簡單的模拟回調,要明白其所表達的含義。Fetcher.fetchData()方法需傳遞一個FetcherCallback類型的參數,當獲得資料或發生錯誤時被回調。對于每種情況都提供了同意的方法:

FetcherCallback.onData(),将接收資料時被調用

FetcherCallback.onError(),發生錯誤時被調用

因為可以将這些方法的執行從"caller"線程移動到其他的線程執行;但也不會保證FetcherCallback的每個方法都會被執行。回調過程有個問題就是當你使用鍊式調用

很多不同的方法會導緻線性代碼;有些人認為這種鍊式調用方法會導緻代碼難以閱讀,但是我認為這是一種風格和習慣問題。例如,基于Javascript的Node.js越來越受歡迎,它使用了大量的回調,許多人都認為它的這種方式利于閱讀和編寫。

第二種技術是使用Futures。Futures是一個抽象的概念,它表示一個值,該值可能在某一點變得可用。一個Future要麼獲得計算完的結果,要麼獲得計算失敗後的異常。Java在java.util.concurrent包中附帶了Future接口,它使用Executor異步執行。例如下面的代碼,每傳遞一個Runnable對象到ExecutorService.submit()方法就會得到一個回調的Future,你能使用它檢測是否執行完成。

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class FutureExample {

    public static void main(String[] args) throws Exception {

        ExecutorService executor = Executors.newCachedThreadPool();

        Runnable task1 = new Runnable() {

            public void run() {

                //do something

                System.out.println("i am task1.....");

        };

        Callable&lt;Integer&gt; task2 = new Callable&lt;Integer&gt;() {

            public Integer call() throws Exception {

                return new Integer(100);

        Future&lt;?&gt; f1 = executor.submit(task1);

        Future&lt;Integer&gt; f2 = executor.submit(task2);

        System.out.println("task1 is completed? " + f1.isDone());

        System.out.println("task2 is completed? " + f2.isDone());

        //waiting task1 completed

        while(f1.isDone()){

            System.out.println("task1 completed.");

            break;

        //waiting task2 completed

        while(f2.isDone()){

            System.out.println("return value by task2: " + f2.get());

有時候使用Future感覺很醜陋,因為你需要間隔檢查Future是否已完成,而使用回調會直接收到傳回通知。看完這兩個常用的異步執行技術後,你可能想知道使用哪個最好?這裡沒有明确的答案。事實上,Netty兩者都使用,提供兩全其美的方案。下一節将在JVM上首先使用阻塞,然後再使用NIO和NIO2寫一個網絡程式。這些是本書後續章節必不可少的基礎知識,如果你熟悉Java網絡AIPs,你可以快速翻閱即可。

本節主要講解Java的IO和NIO的差異,這裡不過多贅述,網絡已有很多相關文章。

        本節中将介紹Netty是如何解決NIO中的一些問題和限制。Java的NIO相對老的IO APIs有着非常大的進步,但是使用NIO是受限制的。這些問題往往是設計的問題,有些是缺陷知道的。

        NIO是一個比較底層的APIs,它依賴于作業系統的IO APIs。Java實作了統一的接口來操作IO,其在所有作業系統中的工作行為是一樣的,這是很偉大的。使用NIO會經常發現代碼在Linux上正常運作,但在Windows上就會出現問題。我建議你如果使用NIO編寫程式,就應該在所有的作業系統上進行測試來支援,使程式可以在任何作業系統上正常運作;即使在所有的Linux系統上都測試通過了,也要在其他的作業系統上進行測試;你若不驗證,以後就可能會出問題。

        NIO2看起來很理想,但是NIO2隻支援Jdk1.7+,若你的程式在Java1.6上運作,則無法使用NIO2。另外,Java7的NIO2中沒有提供DatagramSocket的支援,是以NIO2隻支援TCP程式,不支援UDP程式。

        Netty提供一個統一的接口,同一語義無論在Java6還是Java7的環境下都是可以運作的,開發者無需關心底層APIs就可以輕松實作相關功能。

        ByteBuffer是一個資料容器,但是可惜的是JDK沒有開發ByteBuffer實作的源碼;ByteBuffer允許包裝一個byte[]來獲得一個執行個體,如果你希望盡量減少記憶體拷貝,那麼這種方式是非常有用的。若果你想将ByteBuffer重新實作,那麼不要浪費你的時間了,ByteBuffer的構造函數是私有的,是以它不能被擴充。Netty提供了自己的ByteBuffer實作,Netty通過一些簡單的APIs對ByteBuffer進行構造、使用和操作,以此來解決NIO中的一些限制。

        很多Channel的實作支援Gather和Scatter。這個功能允許從從多個ByteBuffer中讀入或寫入到過個ByteBuffer,這樣做可以提供性能。作業系統底層知道如何處理這些被寫入/讀出,并且能以最有效的方式處理。如果要分割的資料再多個不同的ByteBuffer中,使用Gather/Scatter是比較好的方式。

        例如,你可能希望header在一個ByteBuffer中,而body在另外的ByteBuffer中;

        下圖顯示的是Scatter(分散),将ScatteringByteBuffer中的資料分散讀取到多個ByteBuffer中:

Netty In Action中文版 - 第一章:Netty介紹Netty In Action中文版 - 第一章:Netty介紹

        下圖顯示的是Gather(聚合),将多個ByteBuffer的資料寫入到GatheringByteChannel:

Netty In Action中文版 - 第一章:Netty介紹Netty In Action中文版 - 第一章:Netty介紹

        可惜Gather/Scatter功能會導緻記憶體洩露,知道Java7才解決記憶體洩露問題。使用這個功能必須小心編碼和Java版本。

        壓碎著名的epoll缺陷。

        On Linux-like OSs the selector makes use of the epoll- IO event notification facility. This is a high-performance technique in which the OS works asynchronously with the networking stack.Unfortunately,  even  today  the "famous" epoll- bug  can  lead  to  an "invalid" state  in  the selector, resulting in 100% CPU-usage and spinning. The only way to recover is to recycle the old  selector  and  transfer  the  previously  registered  Channel  instances  to  the  newly  created Selector.

        Linux-like OSs的選擇器使用的是epoll-IO事件通知工具。這是一個在作業系統以異步方式工作的網絡stack.Unfortunately,即使是現在,著名的epoll-bug也可能會導緻無效的狀态的選擇和100%的CPU使用率。要解決epoll-bug的唯一方法是回收舊的選擇器,将先前注冊的通道執行個體轉移到新建立的選擇器上。

        What  happens  here  is  that  the Selector.select() method  stops  to  block  and  returns immediately-even  if  there  are  no  selected  SelectionKeys  present.  This  is  against  the contract,  which  is  in  the  Javadocs  of  the  Selector.select()  method:Selector.select() must not unblock if nothing is selected.

        這裡發生的是,不管有沒有已選擇的SelectionKey,Selector.select()方法總是不會阻塞并且會立刻傳回。這違反了Javadoc中對Selector.select()方法的描述,Javadoc中的描述:Selector.select() must not unblock if nothing is selected. (Selector.select()方法若未選中任何事件将會阻塞。)

        The range of solutions to this epoll- problem is limited, but Netty attempts to automatically detect and prevent it. The following listing is an example of the epoll- bug.

        NIO中對epoll問題的解決方案是有限制的,Netty提供了更好的解決方案。下面是epoll-bug的一個例子:

...

while (true) {

int selected = selector.select();

Set&lt;SelectedKeys&gt; readyKeys = selector.selectedKeys();

Iterator iterator = readyKeys.iterator();

while (iterator.hasNext()) {

        The effect of this code is that the while loop eats CPU:

        這段代碼的作用是while循環消耗CPU:

        The value will never be false, and the code keeps your CPU spinning and eats resources. This can have some undesirable side effects as it can consume all of your CPU, preventing any other CPU-bound work.

        該值将永遠是假的,代碼将持續消耗你的CPU資源。這會有一些副作用,因為CPU消耗完了就無法再去做其他任何的工作。

        These  are  only  a  few  of  the  possible  problems  you  may  see  while  using  non-blocking  IO. Unfortunately, even  after years of development  in  this area,  issues still need  to be  resolved; thankfully, Netty addresses them for you.

        這些僅僅是在使用NIO時可能會出現的一些問題。不幸的是,雖然在這個領域發展了多年,問題依然存在;幸運的是,Netty給了你解決方案。

This  chapter  provided  an  overview  of  Netty's  features,  design  and  benefits.  I  discussed  the difference  between  blocking  and  non-blocking  processing  to  give  you  a  fundamental understanding of the reasons to use a non-blocking framework. You  learned  how  to  use  the  JDK  API  to  write  network  code  in  both  blocking  and  non-blocking modes. This included the new non-blocking API, which comes with JDK 7. After seeing the NIO APIs in action, it was also important to understand some of the known issues that you may run into. In fact, this is why so many people use Netty: to take care of workarounds and other JVM quirks. In the next chapter, you'll learn the basics of the Netty API and programming model, and, finally, use Netty to write some useful code.

原文位址http://www.bieryun.com/2141.html