來看下面這個圖,當客戶端發起一次Http請求時,服務端的處理流程時怎么樣的?

簡單來說可以分為以下幾個步驟:
- 基于TCP協議建立網路通信,
- 開始向服務端端傳輸資料,
- 服務端接受到資料進行決議,開始處理本次請求邏輯,
- 服務端處理完成后回傳結果給客戶端,
在這個程序中,會涉及到網路IO通信,在傳統的BIO模式下,客戶端向服務端發起一個資料讀取請求,客戶端在收到服務端回傳資料之前,一直處于阻塞狀態,直到服務端回傳資料后完成本次會話,這個程序就叫同步阻塞IO,在BIO模型中如果想實作異步操作,就只能使用多執行緒模型,也就是一個請求對應一個執行緒,這樣就能夠避免服務端的鏈接被一個客戶端占用導致連接數無法提高,
同步阻塞IO主要體現在兩個阻塞點
- 服務端接收客戶端連接時的阻塞,
- 客戶端和服務端的IO通信時,資料未就緒的情況下的阻塞,

在這種傳統BIO模式下,會造成一個非常嚴重的問題,如下圖所示,如果同一時刻有N個客戶端發起請求,按照BIO模型的特點,服務端在同一時刻只能處理一個請求,將導致客戶端請求需要排隊處理,帶來的影響是,用戶在等待一次請求處理回傳的時間非常長,意味著服務端沒有并發處理能力,這顯然不合適,

那么,服務端應該如何優化呢?
非阻塞IO
從前面的分析發現,服務端在處理一次請求時,會處于阻塞狀態無法處理后續請求,那是否能夠讓被阻塞的地方優化成不阻塞呢?于是就有了非阻塞IO(NIO)
非阻塞IO,就是客戶端向服務端發起請求時,如果服務端的資料未就緒的情況下, 客戶端請求不會被阻塞,而是直接回傳,但是有可能服務端的資料還未準備好的時候,客戶端收到的回傳是一個空的, 那客戶端怎么拿到最終的資料呢?
如圖所示,客戶端只能通過輪詢的方式來獲得請求結果,NIO相比BIO來說,少了阻塞的程序在性能和連接數上都會有明顯提高,

NIO仍然有一個弊端,就是輪詢程序中會有很多空輪詢,而這個輪詢會存在大量的系統呼叫(發起內核指令從網卡緩沖區中加載資料,用戶空間到內核空間的切換),隨著連接數量的增加,會導致性能問題,
多路復用機制
I/O多路復用的本質是通過一種機制(系統內核緩沖I/O資料),讓單個行程可以監視多個檔案描述符,一旦某個描述符就緒(一般是讀就緒或寫就緒),能夠通知程式進行相應的讀寫操作
什么是fd:在linux中,內核把所有的外部設備都當成是一個檔案來操作,對一個檔案的讀寫會呼叫內核提供的系統命令,回傳一個fd(檔案描述符),而對于一個socket的讀寫也會有相應的檔案描述符,成為socketfd,
常見的IO多路復用方式有【select、poll、epoll】,都是Linux API提供的IO復用方式,那么接下來重點講一下select、和epoll這兩個模型
-
select:行程可以通過把一個或者多個fd傳遞給select系統呼叫,行程會阻塞在select操作上,這樣select可以幫我們檢測多個fd是否處于就緒狀態,這個模式有兩個缺點
- 由于他能夠同時監聽多個檔案描述符,假如說有1000個,這個時候如果其中一個fd 處于就緒狀態了,那么當前行程需要線性輪詢所有的fd,也就是監聽的fd越多,性能開銷越大,
- 同時,select在單個行程中能打開的fd是有限制的,默認是1024,對于那些需要支持單機上萬的TCP連接來說確實有點少
-
epoll:linux還提供了epoll的系統呼叫,epoll是基于事件驅動方式來代替順序掃描,因此性能相對來說更高,主要原理是,當被監聽的fd中,有fd就緒時,會告知當前行程具體哪一個fd就緒,那么當前行程只需要去從指定的fd上讀取資料即可,另外,epoll所能支持的fd上線是作業系統的最大檔案句柄,這個數字要遠遠大于1024
【由于epoll能夠通過事件告知應用行程哪個fd是可讀的,所以我們也稱這種IO為異步非阻塞IO,當然它是偽異步的,因為它還需要去把資料從內核同步復制到用戶空間中,真正的異步非阻塞,應該是資料已經完全準備好了,我只需要從用戶空間讀就行】
I/O多路復用的好處是可以通過把多個I/O的阻塞復用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求,它的最大優勢是系統開銷小,并且不需要創建新的行程或者執行緒,降低了系統的資源開銷,它的整體實作思想如圖2-3所示,
客戶端請求到服務端后,此時客戶端在傳輸資料程序中,為了避免Server端在read客戶端資料程序中阻塞,服務端會把該請求注冊到Selector復路器上,服務端此時不需要等待,只需要啟動一個執行緒,通過selector.select()阻塞輪詢復路器上就緒的channel即可,也就是說,如果某個客戶端連接資料傳輸完成,那么select()方法會回傳就緒的channel,然后執行相關的處理即可,

異步IO
異步IO和多路復用機制,最大的區別在于:當資料就緒后,客戶端不需要發送內核指令從內核空間讀取資料,而是系統會異步把這個資料直接拷貝到用戶空間,應用程式只需要直接使用該資料即可,

在Java中,我們可以使用NIO的api來完成多路復用機制,實作偽異步IO,在網路通信演進模型分析這篇文章中演示了Java API實作多路復用機制的代碼,發現代碼不僅僅繁瑣,而且使用起來很麻煩,
所以Netty出現了,Netty的I/O模型是基于非阻塞IO實作的,底層依賴的是JDK NIO框架的多路復用器Selector來實作,
一個多路復用器Selector可以同時輪詢多個Channel,采用epoll模式后,只需要一個執行緒負責Selector的輪詢,就可以接入成千上萬個客戶端連接,
Reactor模型
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
了解了NIO多路復用后,就有必要再和大家說一下Reactor多路復用高性能I/O設計模式,Reactor本質上就是基于NIO多路復用機制提出的一個高性能IO設計模式,它的核心思想是把回應IO事件和業務處理進行分離,通過一個或者多個執行緒來處理IO事件,然后將就緒得到事件分發到業務處理handlers執行緒去異步非阻塞處理,如圖2-5所示,
Reactor模型有三個重要的組件:
- Reactor :將I/O事件發派給對應的Handler
- Acceptor :處理客戶端連接請求
- Handlers :執行非阻塞讀/寫

這是最基本的單Reactor單執行緒模型(整體的I/O操作是由同一個執行緒完成的),
其中Reactor執行緒,負責多路分離套接字,有新連接到來觸發connect 事件之后,交由Acceptor進行處理,有IO讀寫事件之后交給hanlder 處理,
Acceptor主要任務就是構建handler ,在獲取到和client相關的SocketChannel之后 ,系結到相應的hanlder上,對應的SocketChannel有讀寫事件之后,基于racotor 分發,hanlder就可以處理了(所有的IO事件都系結到selector上,有Reactor分發)
Reactor 模式本質上指的是使用
I/O 多路復用(I/O multiplexing) + 非阻塞 I/O(non-blocking I/O)的模式,
多執行緒單Reactor模型
單執行緒Reactor這種實作方式有存在著缺點,從實體代碼中可以看出,handler的執行是串行的,如果其中一個handler處理執行緒阻塞將導致其他的業務處理阻塞,由于handler和reactor在同一個執行緒中的執行,這也將導致新的無法接收新的請求,我們做一個小實驗:
- 在上述Reactor代碼的DispatchHandler的run方法中,增加一個Thread.sleep(),
- 打開多個客戶端視窗連接到Reactor Server端,其中一個視窗發送一個資訊后被阻塞,另外一個視窗再發資訊時由于前面的請求阻塞導致后續請求無法被處理,
為了解決這種問題,有人提出使用多執行緒的方式來處理業務,也就是在業務處理的地方加入執行緒池異步處理,將reactor和handler在不同的執行緒來執行,如圖4-7所示,

多執行緒多Reactor模型
在多執行緒單Reactor模型中,我們發現所有的I/O操作是由一個Reactor來完成,而Reactor運行在單個執行緒中,它需要處理包括Accept()/read()/write/connect操作,對于小容量的場景,影響不大,但是對于高負載、大并發或大資料量的應用場景時,容易成為瓶頸,主要原因如下:
- 一個NIO執行緒同時處理成百上千的鏈路,性能上無法支撐,即便NIO執行緒的CPU負荷達到100%,也無法滿足海量訊息的讀取和發送;
- 當NIO執行緒負載過重之后,處理速度將變慢,這會導致大量客戶端連接超時,超時之后往往會進行重發,這更加重了NIO執行緒的負載,最侄訓導致大量訊息積壓和處理超時,成為系統的性能瓶頸;
所以,我們還可以更進一步優化,引入多Reactor多執行緒模式,如圖2-7所示,Main Reactor負責接收客戶端的連接請求,然后把接收到的請求傳遞給SubReactor(其中subReactor可以有多個),具體的業務IO處理由SubReactor完成,
Multiple Reactors 模式通常也可以等同于 Master-Workers 模式,比如 Nginx 和 Memcached 等就是采用這種多執行緒模型,雖然不同的專案實作細節略有區別,但總體來說模式是一致的,

- Acceptor,請求接收者,在實踐時其職責類似服務器,并不真正負責連接請求的建立,而只將其請求委托 Main Reactor 執行緒池來實作,起到一個轉發的作用,
- Main Reactor,主 Reactor 執行緒組,主要負責連接事件,并將IO讀寫請求轉發到 SubReactor 執行緒池,
- Sub Reactor,Main Reactor 通常監聽客戶端連接后會將通道的讀寫轉發到 Sub Reactor 執行緒池中一個執行緒(負載均衡),負責資料的讀寫,在 NIO 中 通常注冊通道的讀(OP_READ)、寫事件(OP_WRITE),
高性能通信框架之Netty
在Java中,網路編程框架有很多,比如Java NIO、Mina、Netty、Grizzy等,但是在大家接觸到的所有中間件中,絕大部分都是采用Netty,
原因是Netty是目前最流行的一款高性能Java網路編程框架,它被廣泛參考在中間件、直播、社交、游戲等領域,談及到開源中間件,大家熟知的Dubbo、RocketMQ、Elasticsearch、Hbase、RocketMQ等都是采用Netty實作,
在實際開發中,今天來聽課的同學,99%的人都不會涉及到使用Netty做網路編程開發,但是為什么還要花精力給大家講呢?原因有幾個
- 在很多大廠面試的時候,會涉及到相關的知識點
- Netty高性能表現在哪些方面
- Netty中有哪些重要組件
- Netty的記憶體池、物件池的設計
- 很多中間件都是用netty來做網路通信,那么我們在分析這些中間件的原始碼時,降低網路通信的理解難度
- 提升Java知識體系,盡可能的實作對技術體系理解的全面性,
為什么選擇Netty
Netty其實就是一個高性能NIO框架,所以它是基于NIO基礎上的封裝,本質上是提供高性能網路IO通信的功能,由于前面的課程中我們已經詳細的對網路通信做了分析,因此在學習Netty時,學習起來應該是更輕松的,
Netty提供了上述三種Reactor模型的支持,我們可以通過Netty封裝好的API來快速完成不同Reactor模型的開發,這也是為什么大家都選擇Netty的原因之一,除此之外,Netty相比于NIO原生API,它有以下特點:
- 提供了高效的I/O模型、執行緒模型和時間處理機制
- 提供了非常簡單易用的API,相比NIO來說,針對基礎的Channel、Selector、Sockets、Buffers等api提供了更高層次的封裝,屏蔽了NIO的復雜性
- 對資料協議和序列化提供了很好的支持
- 穩定性,Netty修復了JDK NIO較多的問題,比如select空轉導致的cpu消耗100%、TCP斷線重連、keep-alive檢測等問題,
- 可擴展性在同型別的框架中都是做的非常好的,比如一個是可定制化的執行緒模型,用戶可以在啟動引數中選擇Reactor模型、 可擴展的事件驅動模型,將業務和框架的關注點分離,
- 性能層面的優化,作為網路通信框架,需要處理大量的網路請求,必然就面臨網路物件需要創建和銷毀的問題,這種對JVM的GC來說不是很友好,為了降低JVM垃圾回收的壓力,引入了兩種優化機制
- 物件池復用,
- 零拷貝技術
Netty的生態介紹
首先,我們需要去了解Netty到底提供了哪些功能,如圖2-1所示,表示Netty生態中提供的功能說明,后續內容中會逐步的分析這些功能,

Netty的基本使用
需要說明一下,我們講解的Netty版本是4.x版本,之前有一段時間netty發布了一個5.x版本,但是被官方舍棄了,原因是:使用ForkJoinPool增加了復雜性,并且沒有顯示出明顯的性能優勢,同時保持所有的分支同步是相當多的作業,沒有必要,
添加jar包依賴
使用4.1.66版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
創建Netty Server服務
大部分場景中,我們使用的主從多執行緒Reactor模型,Boss執行緒是住Reactor,Worker是從Reactor,他們分別使用不同的NioEventLoopGroup
主Reactor負責處理Accept,然后把Channel注冊到從Reactor,從Reactor主要負責Channel生命周期內的所有I/O事件,
public class NettyBasicServerExample {
public void bind(int port){
// 我們要創建兩個EventLoopGroup,
// 一個是boss專門用來接收連接,可以理解為處理accept事件,
// 另一個是worker,可以關注除了accept之外的其它事件,處理子任務,
//上面注意,boss執行緒一般設定一個執行緒,設定多個也只會用到一個,而且多個目前沒有應用場景,
// worker執行緒通常要根據服務器調優,如果不寫默認就是cpu的兩倍,
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
//服務端要啟動,需要創建ServerBootStrap,
// 在這里面netty把nio的模板式的代碼都給封裝好了
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup) //配置boss和worker執行緒
//配置Server的通道,相當于NIO中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
//childHandler表示給worker那些執行緒配置了一個處理器,
// 配置初始化channel,也就是給worker執行緒配置對應的handler,當收到客戶端的請求時,分配給指定的handler處理
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NormalMessageHandler()); //添加handler,也就是具體的IO事件處理器
}
});
//由于默認情況下是NIO異步非阻塞,所以系結埠后,通過sync()方法阻塞直到連接建立
//系結埠并同步等待客戶端連接(sync方法會阻塞,直到整個啟動程序完成)
ChannelFuture channelFuture=bootstrap.bind(port).sync();
System.out.println("Netty Server Started,Listening on :"+port);
//等待服務端監聽埠關閉
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//釋放執行緒資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyBasicServerExample().bind(8080);
}
}
上述代碼說明如下:
- EventLoopGroup,定義執行緒組,相當于我們之前在寫NIO代碼時定義的執行緒,這里定義了兩個執行緒組分別是boss執行緒和worker執行緒,boss執行緒負責接收連接,worker執行緒負責處理IO事件,boss執行緒一般設定一個執行緒,設定多個也只會用到一個,而且多個目前沒有應用場景,而worker執行緒通常要根據服務器調優,如果不寫默認就是cpu的兩倍,
- ServerBootstrap,服務端要啟動,需要創建ServerBootStrap,在這里面netty把nio的模板式的代碼都給封裝好了,
- ChannelOption.SO_BACKLOG
設定Channel型別
NIO模型是Netty中最成熟也是被廣泛參考的模型,因此在使用Netty的時候,我們會采用NioServerSocketChannel作為Channel型別,
bootstrap.channel(NioServerSocketChannel.class);
除了NioServerSocketChannel以外,還提供了
- EpollServerSocketChannel,epoll模型只有在linux kernel 2.6以上才能支持,在windows和mac都是不支持的,如果設定Epoll在window環境下運行會報錯,
- OioServerSocketChannel,用于服務端阻塞地接收TCP連接
- KQueueServerSocketChannel,kqueue模型,是Unix中比較高效的IO復用技術,常見的IO復用技術有select, poll, epoll以及kqueue等等,其中epoll為Linux獨占,而kqueue則在許多UNIX系統上存在,
注冊ChannelHandler
在Netty中可以通過ChannelPipeline注冊多個ChannelHandler,該handler就是給到worker執行緒執行的處理器,當IO事件就緒時,會根據這里配置的Handler進行呼叫,
這里可以注冊多個ChannelHandler,每個ChannelHandler各司其職,比如做編碼和解碼的handler,心跳機制的handler,訊息處理的handler等,這樣可以實作代碼的最大化復用,
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NormalMessageHandler());
}
});
ServerBootstrap中的childHandler方法需要注冊一個ChannelHandler,這里配置了一個ChannelInitializer的實作類,通過實體化ChannelInitializer來配置初始化Channel,
當收到IO事件后,這個資料會在這多個handler中進行傳播,上述代碼中配置了一個NormalMessageHandler,用來接收客戶端訊息并輸出,
系結埠
完成Netty的基本配置后,通過bind()方法真正觸發啟動,而sync()方法會阻塞,直到整個啟動程序完成,
ChannelFuture channelFuture=bootstrap.bind(port).sync();
NormalMessageHandler
ServerHandler繼承了ChannelInboundHandlerAdapter,這是netty中的一個事件處理器,netty中的處理器分為Inbound(進站)和Outbound(出站)處理器,后面會詳細介紹,
public class NormalMessageHandler extends ChannelInboundHandlerAdapter {
//channelReadComplete方法表示訊息讀完了的處理,writeAndFlush方法表示寫入并發送訊息
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//這里的邏輯就是所有的訊息讀取完畢了,在統一寫回到客戶端,Unpooled.EMPTY_BUFFER表示空訊息,addListener(ChannelFutureListener.CLOSE)表示寫完后,就關閉連接
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
//exceptionCaught方法就是發生例外的處理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
//channelRead方法表示讀到訊息以后如何處理,這里我們把訊息列印出來
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in=(ByteBuf) msg;
byte[] req=new byte[in.readableBytes()];
in.readBytes(req); //把資料讀到byte陣列中
String body=new String(req,"UTF-8");
System.out.println("服務器端收到訊息:"+body);
//寫回資料
ByteBuf resp=Unpooled.copiedBuffer(("receive message:"+body+"").getBytes());
ctx.write(resp);
//ctx.write表示把訊息再發送回客戶端,但是僅僅是寫到緩沖區,沒有發送,flush才會真正寫到網路上去
}
}
通過上述代碼發現,我們只需要通過極少的代碼就完成了NIO服務端的開發,相比傳統的NIO原生類別庫的服務端,代碼量大大減少,開發難度也大幅度降低,
Netty和NIO的api對應
TransportChannel ----對應NIO中的channel
EventLoop---- 對應于NIO中的while回圈
EventLoopGroup: 多個EventLoop,就是事件回圈
ChannelHandler和ChannelPipeline---對應于NIO中的客戶邏輯實作handleRead/handleWrite(interceptor pattern)
ByteBuf---- 對應于NIO 中的ByteBuffer
Bootstrap 和 ServerBootstrap ---對應NIO中的Selector、ServerSocketChannel等的創建、配置、啟動等
Netty的整體作業機制
Netty的整體作業機制如下,整體設計就是前面我們講過的多執行緒Reactor模型,分離請求監聽和請求處理,通過多執行緒分別執行具體的handler,

網路通信層
網路通信層主要的職責是執行網路的IO操作,它支持多種網路通信協議和I/O模型的鏈接操作,當網路資料讀取到內核緩沖區后,會觸發讀寫事件,這些事件在分發給時間調度器來進行處理,
在Netty中,網路通信的核心組件以下三個組件
- Bootstrap, 客戶端啟動api,用來鏈接遠程netty server,只系結一個EventLoopGroup
- ServerBootStrap,服務端監聽api,用來監聽指定埠,會系結兩個EventLoopGroup, bootstrap組件可以非常方便快捷的啟動Netty應用程式
- Channel,Channel是網路通信的載體,Netty自己實作的Channel是以JDK NIO channel為基礎,提供了更高層次的抽象,同時也屏蔽了底層Socket的復雜性,為Channel提供了更加強大的功能,
如圖2-3所示,表示的是Channel的常用實作實作類關系圖,AbstractChannel是整個Channel實作的基類,派生出了AbstractNioChannel(非阻塞io)、AbstractOioChannel(阻塞io),每個子類代表了不同的I/O模型和協議型別,

隨著連接和資料的變化,Channel也會存在多種狀態,比如連接建立、連接注冊、連接讀寫、連接銷毀,隨著狀態的變化,Channel也會處于不同的生命周期,每種狀態會系結一個相應的事件回呼,以下是常見的時間回呼方法,
- channelRegistered, channel創建后被注冊到EventLoop上
- channelUnregistered,channel創建后未注冊或者從EventLoop取消注冊
- channelActive,channel處于就緒狀態,可以被讀寫
- channelInactive,Channel處于非就緒狀態
- channelRead,Channel可以從源端讀取資料
- channelReadComplete,Channel讀取資料完成
簡單總結一下,Bootstrap和ServerBootStrap分別負責客戶端和服務端的啟動,Channel是網路通信的載體,它提供了與底層Socket互動的能力,
而當Channel生命周期中的事件變化,就需要觸發進一步處理,這個處理是由Netty的事件調度器來完成,
事件調度器
事件調度器是通過Reactor執行緒模型對各類事件進行聚合處理,通過Selector主回圈執行緒集成多種事件(I/O時間、信號時間),當這些事件被觸發后,具體針對該事件的處理需要給到服務編排層中相關的Handler來處理,
事件調度器核心組件:
-
EventLoopGroup,相當于執行緒池
-
EventLoop,相當于執行緒池中的執行緒
EventLoopGroup本質上是一個執行緒池,主要負責接收I/O請求,并分配執行緒執行處理請求,為了更好的理解EventLoopGroup、EventLoop、Channel之間的關系,我們來看圖2-4所示的流程,

從圖中可知
- 一個EventLoopGroup可以包含多個EventLoop,EventLoop用來處理Channel生命周期內所有的I/O事件,比如accept、connect、read、write等
- EventLoop同一時間會與一個執行緒系結,每個EventLoop負責處理多個Channel
- 每新建一個Channel,EventLoopGroup會選擇一個EventLoop進行系結,該Channel在生命周期內可以對EventLoop進行多次系結和解綁,
圖2-5表示的是EventLoopGroup的類關系圖,可以看出Netty提供了EventLoopGroup的多種實作,如NioEventLoop、EpollEventLoop、NioEventLoopGroup等,
從圖中可以看到,EventLoop是EventLoopGroup的子介面,我們可以把EventLoop等價于EventLoopGroup,前提是EventLoopGroup中只包含一個EventLoop,
EventLoopGroup是Netty的核心處理引擎,它和前面我們講解的Reactor執行緒模型有什么關系呢?其實,我們可以簡單的把EventLoopGroup當成是Netty中Reactor執行緒模型的具體實作,我們可以通過配置不同的EventLoopGroup使得Netty支持多種不同的Reactor模型,
- 單執行緒模型,EventLoopGroup只包含一個EventLoop,Boss和Worker使用同一個EventLoopGroup,
- 多執行緒模型:EventLoopGroup包含多個EventLoop,Boss和Worker使用同一個EventLoopGroup,
- 主從多執行緒模型:EventLoopGroup包含多個EventLoop,Boss是主Reactor,Worker是從Reactor模型,他們分別使用不同的EventLoopGroup,主Reactor負責新的網路連接Channel的創建(也就是連接的事件),主Reactor收到客戶端的連接后,交給從Reactor來處理,
服務編排層
服務編排層的職責是負責組裝各類的服務,簡單來說,就是I/O事件觸發后,需要有一個Handler來處理,所以服務編排層可以通過一個Handler處理鏈來實作網路事件的動態編排和有序的傳播,
它包含三個組件
-
ChannelPipeline,它采用了雙向鏈表將多個Channelhandler鏈接在一起,當I/O事件觸發時,ChannelPipeline會依次呼叫組裝好的多個ChannelHandler,實作對Channel的資料處理,ChannelPipeline是執行緒安全的,因為每個新的Channel都會系結一個新的ChannelPipeline,一個ChannelPipeline關聯一個EventLoop,而一個EventLoop只會系結一個執行緒,如圖2-6所示,表示ChannelPIpeline結構圖,
圖2-6 ChannelPipeline 從圖中可以看出,ChannelPipeline中包含入站ChannelInBoundHandler和出站ChannelOutboundHandler,前者是接收資料,后者是寫出資料,其實就是InputStream和OutputStream,為了更好的理解,我們來看圖2-7,

-
ChannelHandler, 針對IO資料的處理器,資料接收后,通過指定的Handler進行處理,
-
ChannelHandlerContext,ChannelHandlerContext用來保存ChannelHandler的背景關系資訊,也就是說,當事件被觸發后,多個handler之間的資料,是通過ChannelHandlerContext來進行傳遞的,ChannelHandler和ChannelHandlerContext之間的關系,如圖2-8所示,
每個ChannelHandler都對應一個自己的ChannelHandlerContext,它保留了ChannelHandler所需要的背景關系資訊,多個ChannelHandler之間的資料傳遞,是通過ChannelHandlerContext來實作的,

以上就是Netty中核心的組件的特性和作業機制的介紹,后續的內容中還會詳細的分析這幾個組件,可以看出,Netty的架構分層設計是非常合理的,它屏蔽了底層NIO以及框架層的實作細節,對于業務開發者來說,只需要關心業務邏輯的編排和實作即可,
組件關系及原理總結
如圖2-9所示,表示Netty中關鍵的組件協調原理,具體的作業機制描述如下,
- 服務單啟動初始化Boss和Worker執行緒組,Boss執行緒組負責監聽網路連接事件,當有新的連接建立時,Boss執行緒會把該連接Channel注冊系結到Worker執行緒
- Worker執行緒組會分配一個EventLoop負責處理該Channel的讀寫事件,每個EventLoop相當于一個執行緒,通過Selector進行事件回圈監聽,
- 當客戶端發起I/O事件時,服務端的EventLoop講就緒的Channel分發給Pipeline,進行資料的處理
- 資料傳輸到ChannelPipeline后,從第一個ChannelInBoundHandler進行處理,按照pipeline鏈逐個進行傳遞
- 服務端處理完成后要把資料寫回到客戶端,這個寫回的資料會在ChannelOutboundHandler組成的鏈中傳播,最后到達客戶端,

Netty中核心組件的詳細介紹
在2.5節中對Netty有了一個全域認識后,我們再針對這幾個組件做一個非常詳細的說明,加深大家的理解,
啟動器Bootstrap和ServerBootstrap作為Netty構建客戶端和服務端的路口,是撰寫Netty網路程式的第一步,它可以讓我們把Netty的核心組件像搭積木一樣組裝在一起,在Netty Server端構建的程序中,我們需要關注三個重要的步驟
- 配置執行緒池
- Channel初始化
- Handler處理器構建
著作權宣告:本博客所有文章除特別宣告外,均采用 CC BY-NC-SA 4.0 許可協議,轉載請注明來自
Mic帶你學架構!
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力,歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術干貨!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/354410.html
標籤:Java
