Netty核心原理
1. Netty介紹
1.1 原生NIO存在的問題
- NIO的類庫和API使用繁雜
- 需要具備其他額外的技能,如java多執行緒程式設計等才能編寫出高質量的NIO程式
- 開發工作量和難度都非常大:例如客戶端面臨斷連重連,半包讀寫,網路擁塞和異常流等情況的處理
- JDK NIO的BUG:Epoll BUG,它會導致Selector空輪詢,最終導致CPU 100%。直到JDK 1.7版本問題仍舊存在,沒有被根本解決。
1.2 概述
Netty是由JBOSS提供的一個java開源框架。Netty提供非同步的、基於事件驅動的網路應用程式框架,用以快速開發高效能、高可靠性的網路IO程式。Netty是一個基於NIO的網路程式設計框架,使用Netty可以幫助你快速、簡單的開發出一個網路應用,相當於簡化和流程化了NIO的開發過程。知名的Elasticsearch、Dubbo框架內部都採用了Netty。
Netty具備如下優點:
- 設計優雅,提供阻塞和非阻塞的Socket;提供靈活可擴充套件的事件模型;提供高度可定製的執行緒模型。
- 具備更高的效能和更大的吞吐量,使用零拷貝技術最小化不必要的記憶體複製
- 提供安全傳輸特性
- 支援多種主流協議,預置多種編解碼功能,支援使用者開發私有協議
2. 執行緒模型
2.1 傳統阻塞I/O服務模型
採用阻塞IO模式獲取輸入的資料,每個連線都需要獨立的執行緒完成資料的輸入,業務處理和資料返回工作。
存在問題:
- 當併發數很大,就會建立大量的執行緒,佔用很大的系統資源
- 連線建立後,如果當前執行緒暫時沒有資料可讀,該執行緒會阻塞在read操作
2.2 Reactor模型
Reactor模式,通過一個或多個輸入同時傳遞給服務處理器的模式,服務端程式處理傳入的多個請求,將他們同步分派到相應的處理執行緒,因此Reactor模式也叫Dispatcher模式。Reactor模式使用IO複用監聽模式,收到事件後,分發給某個執行緒,這點就是網路伺服器高併發處理關鍵。
1)單Reactor單執行緒
- Selector是可以實現應用程式通過一個阻塞物件監聽多路連線請求
- Reactor物件通過Selector監控客戶端請求事件,收到事件後通過Dispatch進行分發
- 如果是建立連線請求事件,則由Acceptor通過Accept處理連線請求,然後建立一個Handler物件處理連線完成後的後續業務處理
- Handler會完成Read->業務處理->Send的完整業務流程
優點:
- 模型簡單,沒有多執行緒競爭通訊的問題
缺點:
- 效能問題:只有一個執行緒,無法完全發揮出多核CPU的效能。Handler在處理某個連線上的業務時,整個程式無法處理其他連線事件,很容易導致效能瓶頸。
- 可靠性問題:執行緒意外終止或進入死迴圈,會導致整個系統通訊模組不可用
2)單Reactor多執行緒
- Reactor物件通過selector監控客戶端請求事件,收到事件後,通過dispatch進行分發
- 如果是建立連線請求,則由Acceptor通過accept處理連線請求
- 如果不是連線請求,則由reactor分發呼叫連線相應的handler來處理
- handler只負責響應事件,不做具體的業務處理,通過read讀取資料後,會分發給後面的worker執行緒池來處理
優點:
- 可以充分的利用多核CPU的處理能力
缺點:
- 多執行緒資料共享和訪問比較複雜,reactor處理所有的事件的監聽和響應,在單執行緒執行,在高併發場景容易出現效能瓶頸
3)主從Reactor多執行緒
- Reactor主執行緒MainReactor物件通過selector監聽客戶端連線事件,收到事件後,通過Acceptor處理客戶端連線事件
- 當Acceptor處理完連線事件後,MainReactor將連線分為SubReactor
- SubReactor將連線加入到自己的連線佇列進行監聽,並建立Handler對各種事件進行處理
- 當連線上有新事件發生的時候,SubReactor就會呼叫對應的Handler處理
- Handler通過read從連線上讀取請求資料,將請求資料分發給Worker執行緒池就緒業務處理。
優點:
- MainReactor執行緒與SubReactor執行緒的資料互動簡單職責明確,MainReactor執行緒只需要接收新連線,SubReactor執行緒完成後續的業務處理
- MainReactor執行緒與SubReactor執行緒的資料互動簡單,MainReactor執行緒只需要把新連線傳給SubReactor執行緒,SubReactor執行緒無需返回資料
- 多個SubReactor執行緒能夠應對更高的併發請求
缺點:
- 這種模式程式設計複雜度較高。
這種模式被廣泛使用,包括Nginx、Memcached、Netty等。這種模式也叫做1+M+N執行緒模式,即使用該模式開發的伺服器包含1個(或多個,1只是表示相對較少)連線建立執行緒+M個IO執行緒+N個業務處理執行緒。
2.3 Netty執行緒模型
Netty中我們使用最多的還是主從Reactor執行緒模型。
- BossGroup中的執行緒專門負責和客戶端建立連線
- WorkerGroup中的執行緒負責處理連線上的讀寫
3 入門案例
- 引入依賴
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.72.Final</version>
</dependency>
- 服務端程式碼
public class DemoNettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup=new NioEventLoopGroup(1);
//不填寫執行緒數,預設是2*處理器執行緒數
EventLoopGroup workerGroup=new NioEventLoopGroup();
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
//設定服務端通道實現
.channel(NioServerSocketChannel.class)
//設定執行緒佇列中等待連線個數
.option(ChannelOption.SO_BACKLOG,128)
//設定活躍狀態,child是設定wokerGroup
.childOption(ChannelOption.SO_KEEPALIVE,Boolean.TRUE)
.childHandler(new ChannelInitializer<SocketChannel>() {
//建立一個通道初始化物件
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DemoNettyServerHandle());
}
});
//啟動服務端並繫結埠,將非同步改為同步
ChannelFuture channelFuture = bootstrap.bind(9999).sync();
System.out.println("服務端啟動成功");
//關閉通道(不是真正意義上的關閉,而是監聽通道關閉狀態)
channelFuture.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public class DemoNettyServerHandle implements ChannelInboundHandler {
@Override
public void channelRegistered(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void channelUnregistered(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {
}
/**
* 通道讀取事件
* @param channelHandlerContext
* @param o
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
ByteBuf byteBuf=(ByteBuf)o;
System.out.println("客戶端發來訊息:"+byteBuf.toString(CharsetUtil.UTF_8));
}
/**
* 讀取完畢事件
* @param channelHandlerContext
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("你好,我是Netty服務端",CharsetUtil.UTF_8));
}
@Override
public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void handlerAdded(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void handlerRemoved(ChannelHandlerContext channelHandlerContext) throws Exception {
}
/**
* 異常發生事件
* @param channelHandlerContext
* @param throwable
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
throwable.printStackTrace();
channelHandlerContext.close();
}
}
- 客戶端程式碼
public class DemoNettyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group=new NioEventLoopGroup();
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DemoNettyLientHandle());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();
channelFuture.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
public class DemoNettyLientHandle implements ChannelInboundHandler {
@Override
public void channelRegistered(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void channelUnregistered(ChannelHandlerContext channelHandlerContext) throws Exception {
}
/**
* 通道就緒事件
* @param channelHandlerContext
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("你好,我是客戶端",CharsetUtil.UTF_8));
}
@Override
public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
ByteBuf byteBuf=(ByteBuf)o;
System.out.println("服務端發來訊息:"+byteBuf.toString(CharsetUtil.UTF_8));
}
@Override
public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void handlerAdded(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void handlerRemoved(ChannelHandlerContext channelHandlerContext) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
}
}