前言
在之前的 SpringBoot 整合長連接心跳機制 一文中認識了 Netty。
但其實只是能用,為什么要用 Netty?它有哪些優勢?這些其實都不清楚。
本文就來從歷史源頭說道說道。
傳統 IO
在 Netty 以及 NIO 出現之前,我們寫 IO 應用其實用的都是用 java.io.*
下所提供的包。
比如下面的偽代碼:
ServeSocket serverSocket = new ServeSocket(8080);
Socket socket = serverSocket.accept() ;
BufferReader in = .... ;
String request ;
while((request = in.readLine()) != null){
new Thread(new Task()).start()
}
大概是這樣,其實主要想表達的是:這樣一個線程只能處理一個連接。
如果是 100 個客戶端連接那就得開 100 個線程,1000 那就得 1000 個線程。
要知道線程資源非常寶貴,每次的創建都會帶來消耗,而且每個線程還得為它分配對應的棧內存。
即便是我們給 JVM 足夠的內存,大量線程所帶來的上下文切換也是受不了的。
並且傳統 IO 是阻塞模式,每一次的響應必須的是發起 IO 請求,處理請求完成再同時返回,直接的結果就是性能差,吞吐量低。
Reactor 模型
因此業界常用的高性能 IO 模型是 Reactor
。
它是一種異步、非阻塞的事件驅動模型。
通常也表現為以下三種方式:
單線程
從圖中可以看出:
它是由一個線程來接收客戶端的連接,並將該請求分發到對應的事件處理 handler 中,整個過程完全是異步非阻塞的;並且完全不存在共享資源的問題。所以理論上來說吞吐量也還不錯。
但由於是一個線程,對多核 CPU 利用率不高,一旦有大量的客戶端連接上來性能必然下降,甚至會有大量請求無法響應。
最壞的情況是一旦這個線程哪里沒有處理好進入了死循環那整個服務都將不可用!
多線程
因此產生了多線程模型。
其實最大的改進就是將原有的事件處理改為了多線程。
可以基於 Java 自身的線程池實現,這樣在大量請求的處理上性能提示是巨大的。
雖然如此,但理論上來說依然有一個地方是單點的;那就是處理客戶端連接的線程。
因為大多數服務端應用或多或少在連接時都會處理一些業務,如鑒權之類的,當連接的客戶端越來越多時這一個線程依然會存在性能問題。
於是又有了下面的線程模型。
主從多線程
該模型將客戶端連接那一塊的線程也改為多線程,稱為主線程。
同時也是多個子線程來處理事件響應,這樣無論是連接還是事件都是高性能的。
Netty 實現
以上談了這么多其實 Netty 的線程模型與之的類似。
我們回到之前 SpringBoot 整合長連接心跳機制 中的服務端代碼:
private EventLoopGroup boss = new NioEventLoopGroup();
private EventLoopGroup work = new NioEventLoopGroup();
/**
* 啟動 Netty
*
* @return
* @throws InterruptedException
*/
@PostConstruct
public void start() throws InterruptedException {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(boss, work)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(nettyPort))
//保持長連接
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new HeartbeatInitializer());
ChannelFuture future = bootstrap.bind().sync();
if (future.isSuccess()) {
LOGGER.info("啟動 Netty 成功");
}
}
其實這里的 boss 就相當於 Reactor 模型中處理客戶端連接的線程池。
work 自然就是處理事件的線程池了。
那么如何來實現上文的三種模式呢?其實也很簡單:
單線程模型:
private EventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(group)
.childHandler(new HeartbeatInitializer());
多線程模型:
private EventLoopGroup boss = new NioEventLoopGroup(1);
private EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(boss,work)
.childHandler(new HeartbeatInitializer());
主從多線程:
private EventLoopGroup boss = new NioEventLoopGroup();
private EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(boss,work)
.childHandler(new HeartbeatInitializer());
相信大家一看也明白。
總結
其實看過了 Netty 的線程模型之后能否對我們平時做高性能應用帶來點啟發呢?
我認為是可以的:
- 接口同步轉異步處理。
- 回調通知結果。
- 多線程提高並發效率。
無非也就是這些,只是做了這些之后就會帶來其他問題:
- 異步之后事務如何保證?
- 回調失敗的情況?
- 多線程所帶來的上下文切換、共享資源的問題。
這就是一個博弈的過程,想要做到一個盡量高效的應用是需要不斷磨合試錯的。
上文相關的代碼:
https://github.com/crossoverJie/netty-action
歡迎關注公眾號一起交流: