Netty(3.X)
有了Netty,你可以實現自己的HTTP伺服器,FTP伺服器,UDP伺服器,RPC伺服器,WebSocket伺服器,Redis的Proxy伺服器,MySQL的Proxy伺服器等等。
如果你想知道Nginx是怎麼寫出來的,如果你想知道Tomcat和Jetty是如何實現的,如果你也想實現一個簡單的Redis伺服器,那都應該好好理解一下Netty,它們高效能的原理都是類似的。
看一下傳統的HTTP伺服器的原理:
建立一個ServerSocket,監聽並繫結一個埠
一系列客戶端來請求這個埠
伺服器使用Accept,獲得一個來自客戶端的Socket連線物件
啟動一個新執行緒處理連線
- 讀Socket,得到位元組流
- 解碼協議,得到Http請求物件
- 處理Http請求,得到一個結果,封裝成一個HttpResponse物件
- 編碼協議,將結果序列化位元組流
- 寫Socket,將位元組流發給客戶端
繼續迴圈步驟3
HTTP伺服器之所以稱為HTTP伺服器,是因為編碼解碼協議是HTTP協議,如果協議是Redis協議,那它就成了Redis伺服器,如果協議是WebSocket,那它就成了WebSocket伺服器,等等。
使用Netty你就可以定製編解碼協議,實現自己的特定協議的伺服器。
上面我們說的是一個傳統的多執行緒伺服器,這個也是Apache處理請求的模式。在高併發環境下,執行緒數量可能會建立太多,作業系統的任務排程壓力大,系統負載也會比較高。那怎麼辦呢?
於是NIO誕生了,NIO並不是Java獨有的概念,NIO代表的一個詞彙叫著IO多路複用。它是由作業系統提供的系統呼叫,早期這個作業系統呼叫的名字是select,但是效能低下,後來漸漸演化成了Linux下的epoll和Mac裡的kqueue。我們一般就說是epoll,因為沒有人拿蘋果電腦作為伺服器使用對外提供服務。而Netty就是基於Java NIO技術封裝的一套框架。為什麼要封裝,因為原生的Java NIO使用起來沒那麼方便,而且還有臭名昭著的bug,Netty把它封裝之後,提供了一個易於操作的使用模式和介面,使用者使用起來也就便捷多了。
那NIO究竟是什麼東西呢?
NIO的全稱是NoneBlocking IO,非阻塞IO,區別與BIO,BIO的全稱是Blocking IO,阻塞IO。那這個阻塞是什麼意思呢?
- Accept是阻塞的,只有新連線來了,Accept才會返回,主執行緒才能繼
- Read是阻塞的,只有請求訊息來了,Read才能返回,子執行緒才能繼續處理
- Write是阻塞的,只有客戶端把訊息收了,Write才能返回,子執行緒才能繼續讀取下一個請求
所以傳統的多執行緒伺服器是BlockingIO模式的,從頭到尾所有的執行緒都是阻塞的。這些執行緒就乾等在哪裡,佔用了作業系統的排程資源,什麼事也不幹,是浪費。
那麼NIO是怎麼做到非阻塞的呢?
它用的是事件機制。它可以用一個執行緒把Accept,讀寫操作,請求處理的邏輯全乾了。如果什麼事都沒得做,它也不會死迴圈,它會將執行緒休眠起來,直到下一個事件來了再繼續幹活,這樣的一個執行緒稱之為NIO執行緒。
while true {
events = takeEvents(fds) // 獲取事件,如果沒有事件,執行緒就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新連結來了
} elif event.isReadable {
request = doRead() // 讀訊息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 寫訊息
}
}
}
Netty是建立在NIO基礎之上,Netty在NIO之上又提供了更高層次的抽象。
在Netty裡面,Accept連線可以使用單獨的執行緒池去處理,讀寫操作又是另外的執行緒池來處理。
Accept連線和讀寫操作也可以使用同一個執行緒池來進行處理。而請求處理邏輯既可以使用單獨的執行緒池進行處理,也可以跟放在讀寫執行緒一塊處理。執行緒池中的每一個執行緒都是NIO執行緒。使用者可以根據實際情況進行組裝,構造出滿足系統需求的併發模型。
Netty提供了內建的常用編解碼器,包括行編解碼器[一行一個請求],字首長度編解碼器[前N個位元組定義請求的位元組長度],可重放解碼器[記錄半包訊息的狀態],HTTP編解碼器,WebSocket訊息編解碼器等等
Netty提供了一些列生命週期回撥介面,當一個完整的請求到達時,當一個連線關閉時,當一個連線建立時,使用者都會收到回撥事件,然後進行邏輯處理。
Netty可以同時管理多個埠,可以使用NIO客戶端模型,這些對於RPC服務是很有必要的。
Netty除了可以處理TCP Socket之外,還可以處理UDP Socket。
在訊息讀寫過程中,需要大量使用ByteBuffer,Netty對ByteBuffer在效能和使用的便捷性上都進行了優化和抽象。
本質:
1)JBoss做的一個Jar包。
2)目的:快速開發高效能、高可靠性的網路伺服器和客戶端程式。
3)優點:提供非同步的、事件驅動的網路應用程式框架和工具。
簡單體驗
public void run() {
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new EchoServerHandler());
}
});
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(port));
}
這裡EchoServerHandler
是其業務邏輯的實現者,大致程式碼如下:
public class EchoServerHandler extends SimpleChannelUpstreamHandler {
@Override
public void messageReceived(
ChannelHandlerContext ctx, MessageEvent e) {
// Send back the received message to the remote peer.
e.getChannel().write(e.getMessage());
}
Netty的事件驅動機制
看看EchoServerHandler
的程式碼,其中的引數:public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
,MessageEvent就是一個事件。這個事件攜帶了一些資訊,例如這裡e.getMessage()
就是訊息的內容,而EchoServerHandler
則描述了處理這種事件的方式。一旦某個事件觸發,相應的Handler則會被呼叫,並進行處理。這種事件機制在UI程式設計裡廣泛應用,而Netty則將其應用到了網路程式設計領域。
在Netty裡,所有事件都來自ChannelEvent
介面,這些事件涵蓋監聽埠、建立連線、讀寫資料等網路通訊的各個階段。而事件的處理者就是ChannelHandler
,這樣,不但是業務邏輯,連網路通訊流程中底層的處理,都可以通過實現ChannelHandler
來完成了。事實上,Netty內部的連線處理、協議編解碼、超時等機制,都是通過handler完成的。
下圖描述了Netty進行事件處理的流程。Channel
是連線的通道,是ChannelEvent的產生者,而ChannelPipeline
可以理解為ChannelHandler的集合。
Netty的原始碼閱讀
org
└── jboss
└── netty
├── bootstrap 配置並啟動服務的類
├── buffer 緩衝相關類,對NIO Buffer做了一些封裝
├── channel 核心部分,處理連線
├── container 連線其他容器的程式碼
├── example 使用示例
├── handler 基於handler的擴充套件部分,實現協議編解碼等附加功能
├── logging 日誌
└── util 工具類
除了之前說到的事件驅動機制之外,Netty的核心功能還包括兩部分:
Zero-Copy-Capable Rich Byte Buffer
零拷貝的Buffer。為什麼叫零拷貝?因為在資料傳輸時,最終處理的資料會需要對單個傳輸層的報文,進行組合或者拆分。NIO原生的ByteBuffer無法做到這件事,而Netty通過提供Composite(組合)和Slice(切分)兩種Buffer來實現零拷貝。這部分程式碼在
org.jboss.netty.buffer
包中。
這裡需要額外注意,不要和作業系統級別的Zero-Copy混淆了, 作業系統中的零拷貝主要是使用者空間和核心空間之間的資料拷貝, NIO中通過DirectBuffer做了實現.Universal Communication API
統一的通訊API。這個是針對Java的Old I/O和New I/O,使用了不同的API而言。Netty則提供了統一的API(
org.jboss.netty.channel.Channel
)來封裝這兩種I/O模型。這部分程式碼在org.jboss.netty.channel
包中。
此外,Protocol Support功能通過handler機制實現。