Java NIO框架Netty教程(十二)
寫在前面:對Netty併發問題的測試和解決完全超出了我的預期,想說的東西越來越多。所以才出現這個中篇,也就是說,一定會有下篇。至於問題點的發現,OneCoder也在努力驗證中。
繼續併發的問題。在《Java NIO框架Netty教程(十一)-併發訪問測試(上)》中,我們測試的其實是一種偽併發的情景。底層實際只有一個Channel在運作,不會出現什麼無響應的問題。而實際的併發不是這個意思的,獨立的客戶端,自然就會有獨立的channel。也就是一個服務端很可能和很多的客戶端建立關係,就有很多的Channel,進行了多次的繫結。下面我們來模擬一下這種場景。
/**
* Netty併發,多connection,socket併發測試
*
* @author lihzh(OneCoder)
* @alia OneCoder
* @Blog http://www.coderli.com
* @date 2012-8-13 下午10:47:28
*/
public class ConcurrencyNettyMultiConnection {
public static void main(String args[]) {
final ClientBootstrap bootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// 設定一個處理服務端訊息和各種訊息事件的類(Handler)
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new ObjectEncoder(),
new ObjectClientHandler()
);
}
});
for (int i = 0; i < 3; i++) {
bootstrap.connect (new InetSocketAddress("127.0.0.1", 8000));
}
}
}
看到這段程式碼,你可能會疑問,這是在做什麼。這個驗證是基於酷殼的文章《Java NIO類庫Selector機制解析(上)》。他是基於Java Selector直接進行的驗證,Netty是在此之上封裝了一層。其實OneCoder也做了Selector層的驗證。
/**
* Selector開啟埠數量測試
*
* @author lihzh
* @alia OneCoder
* @blog http://www.coderli.com
*/
public class ConcurrencySelectorOpen {
/**
* @author lihzh
* @alia OneCoder
* @throws IOException
* @throws InterruptedException
* @date 2012-8-15 下午1:57:56
*/
public static void main(String[] args) throws IOException, InterruptedException {
for (int i = 0; i < 3; i++) {
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("127.0.0.1", 8000));
channel.register(selector, SelectionKey.OP_READ);
}
Thread.sleep(300000);
}
}
同樣,通過Process Explorer去觀察端口占用情況,開始跟酷殼大哥的觀察到的效果一樣。當不啟動Server端,也就是不實際跟Server端建立連結的時候,3次selector open,佔用了6個埠。
當啟動Server端,進行繫結的時候,佔用了9個埠
其實,多的三個,就是實際繫結的8000埠。其餘就是酷殼大哥說的內部Loop連結。也就是說,對於我們實際場景,一次連結需要的埠數是3個。作業系統的埠數和資源當然有有限的,也就是說當我們增大這個連結的時候,錯誤必然會發生了。OneCoder嘗試一下3000次的場景,並沒有出現錯誤,不過這麼下去出錯肯定是必然的。
再看看服務端的情況
這個還是直接通過Selector連線的時候的服務端的情況,除了兩個內部迴環埠以外,都是通過監聽的8000的埠與外界通訊,所以,服務端不會有埠限制的問題。不過,也可以看到,如果對連結不控制,服務端也維持大量的連線耗費系統資源!所以,良好的編碼是多麼的重要。
回到我們的主題,Netty的場景。先使用跟Selector測試一樣的場景,單執行緒,一個bootstrap,連續多次connect看看錯誤和端口占用的情況。程式碼也就是開頭提供的那段程式碼。
看看測試結果,
同樣連線後還是佔用9個埠。如果手動呼叫了channle.close()方法,則會釋放與8000連結的埠,也就是變成6個端口占用。 增大連續連線數到10000。
首先沒有報錯,在每次close channel情況下,客戶端端口占用情況如圖(服務端情況類似)。
可見,並沒有像selector那樣無限的增加下去。這正是Netty中worker的巨大作用,幫我們管理這些瑣碎的連結。具體分析,我們留待下章,您也可以自己研究一下。(OneCoder注:如果沒關閉channel,會發現對8000埠的佔用,會迅速增加。系統會很卡。)
調整測試程式碼,用一個執行緒來模擬一個客戶端的請求。
/**
* Netty併發,多connection,socket併發測試
*
* @author lihzh(OneCoder)
* @alia OneCoder
* @Blog http://www.coderli.com
* @date 2012-8-13 下午10:47:28
*/
public class ConcurrencyNettyMultiConnection {
public static void main(String args[]) {
final ClientBootstrap bootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// 設定一個處理服務端訊息和各種訊息事件的類(Handler)
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new ObjectEncoder(),
new ObjectClientHandler()
);
}
});
for (int i = 0; i < 1000; i++) {
// 連線到本地的8000埠的服務端
Thread t = new Thread(new Runnable() {
@Override
public void run() {
bootstrap.connect(new InetSocketAddress("127.0.0.1", 8000));
try {
Thread.sleep(300000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
}
不出所料,跟微博上問我問題的朋友出現的問題應該是一樣的:
Write: 973
Write: 974
八月 17, 2012 9:57:28 下午 org.jboss.netty.channel.SimpleChannelHandler
警告: EXCEPTION, please implement one.coder.netty.chapter.eight.ObjectClientHandler.exceptionCaught() for proper handling.
java.net.ConnectException: Connection refused: no further information
這個問題,筆者確實尚未分析出原因,僅從端口占用情況來看,跟前面的測試用例跑出的效果基本類似。應該說明不是埠不足導致的,不過OneCoder尚不敢妄下結論。待研究後,我們下回分解吧:)您有任何想法,也可以提供給我,我來進行驗證。