netty前傳:從傳統I/O到Reactor的三種模型
netty前傳:從傳統I/O到Reactor的三種模型
對最近學習netty的一些知識的一個彙總,對前人知識總結的一些個人理解。
一. 傳統阻塞I/O
傳統的阻塞I/O的網路服務端架構如下:
這裡衍生出一些問題:
1.我們在處理客戶端讀到的資料時,會發生阻塞。
2.雖然,我們可以使用多執行緒的方式,來避免第一個問題,但每次收到一個client端的連線請求,就建立一個執行緒,如果client過多,那必然導致執行緒也很多,執行緒的建立、切換、銷燬,消耗都是很大的。
因此,這種模式,不能適用於一些高併發的場景,只適用於一些連線不多,且固定的場景。
二. NIO
正由於傳統I/O的缺點,引申出了NIO,NIO的大致架構如下:
詳解:
1.使用ServerSocketChannel的物件,監聽對應埠,並註冊到Selector中,用於監聽OP_ACCEPT事件。
2.Selector獲取到Client端連線請求,這個過程不阻塞,沒有請求到達的時候,selector.select(time)可以直接返回,然後程式可以去幹自己的事
3.獲取到Client請求後,通過ServerSocketChannel物件,去生成一個SocketChannel物件,並將它註冊到Selector中,用於監聽OP_READ事件。
4.同樣,Selector監聽Client發訊息的讀事件,此過程也不阻塞。收到訊息後,server端讀資料直接從Buffer中獲取,不需要像I/O那樣,使用read(),在那阻塞等待client端的資料。
5.server端拿到資料後,就可以去處理這些資料了,處理完後,Selector繼續監聽新的事件。
Server端的程式碼也很簡單,如下
public class Test { public static void main(String[] args) throws Exception { new Test().server(); } public void server() throws Exception { Selector selector = Selector.open(); ServerSocketChannel server = ServerSocketChannel.open(); server.bind(new InetSocketAddress(5678)); server.configureBlocking(false); server.register(selector, SelectionKey.OP_ACCEPT); while (true) { int keys = selector.select(); if (keys <= 0) { continue; } Set slKeys = selector.selectedKeys(); Iterator iterator = slKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); deal(key, server, selector); iterator.remove(); } } } public void deal(SelectionKey key, ServerSocketChannel server, Selector selector) throws IOException { if (key.isAcceptable()) { SocketChannel channel = server.accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); //實際業務資料操作 } } }
這種方式,解決了I/O的幾個問題
1.不需要每個Client連線就建立新的執行緒,一個selector,就可以輪詢的去獲取到client的連線事件,且不阻塞。
2.資料讀取不需要阻塞,有一個Buffer和SocketChannel綁定了,OP_READ事件到達的時候,直接從Buffer中取資料即可。
但是,NIO依舊存在一些問題:
1.編碼過於繁瑣
2.selector還是有瓶頸,如果Client數量過多,那每次拿到請求事件,然後再去把SocketChannel註冊到selector,然後selector得到讀事件,然後再去讀取資料,處理業務,這個過程是同步執行的,如果資料量大,處理業務複雜,那還是會影響到其他的client端
因此這種方式適用的場景依舊是,client不是特別多,並且業務邏輯,資料處理相對簡單的場景,比如聊天系統等。
針對NIO的一些問題,就產生了netty。針對netty,又有必要介紹reactor的三種執行緒模型
三. Reactor的三種執行緒模型
1.單Reactor單執行緒模型
一個執行緒採用selecor執行所有的accept請求、處理所有業務邏輯。
前面介紹的NIO用的就是這種
缺點:1)單個執行緒不能利用多核cpu優勢
2)不適用高併發場景
3)如果執行緒異常或者有死迴圈,會導致整個系統通訊模組不可用。
2.單Reactor多執行緒模型
一個執行緒通過selector不斷輪詢接收新的請求,將連線交給執行緒池中某個執行緒來處理。
這種方式,其實就是將處理具體業務部分抽離出來,放到執行緒中去處理,但是資料的讀取和傳送,還是在那一個主執行緒中。
缺點同樣是,如果client太多,那麼accept的那個執行緒處理不過來。
3.主從Reactor多執行緒模型(netty就是使用這種)
就是在單reactor多執行緒基礎之上,再弄執行緒,一個執行緒(其實是執行緒池)來專門處理連線請求,一個執行緒(其實是執行緒池)用來處理read和send請求,然後具體的業務,再按照單reactor多執行緒的方式處理。
介紹了這麼多,其實Reactor的這些模型就是在原始NIO基礎之上形成的,總結來說,就是,一個執行緒池用來處理連線請求(mainReactor),一個執行緒池用來處理read和write(subReactor),另外的執行緒池用來處理具體業務邏輯