本系列為 Netty 學(xué)習(xí)筆記得哆,本篇介紹總結(jié)Java NIO 網(wǎng)絡(luò)編程醋火。Netty 作為一個異步的怯晕、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架潜圃,也是基于NIO的客戶、服務(wù)器端的編程框架舟茶。其對 Java NIO 底層 API 進(jìn)行了封裝谭期,因此有必要對 Java 網(wǎng)絡(luò)編程做個大概了解。本篇將重點放在 NIO 網(wǎng)絡(luò)編程模型上吧凉,對 BIO 及 AIO 僅做簡要說明隧出;
1. Java 網(wǎng)絡(luò)編程
最早期的 Java API(java.net)只支持由本地系統(tǒng)套接字庫提供的阻塞函數(shù),其弊端有:一個線程只能處理一條連接客燕;在任何時候都可能有大量線程處于休眠狀態(tài)鸳劳,可能造成資源浪費;需要為每個線程的調(diào)用棧分配內(nèi)存也搓;上下文切換帶來的開銷會很麻煩赏廓;
2002年,JDK 1.4 在 java.nio 包中引入非阻塞 IO(NIO)傍妒。使用事件通知 API 以確定在一組非阻塞套接字中有哪些已經(jīng)就緒能夠進(jìn)行 IO 相關(guān)的操作幔摸;
非阻塞 IO 的優(yōu)勢有:使用較少線程便可處理許多連接,減少了內(nèi)存管理和上下文切換帶來的開銷颤练;沒有 IO 操作需要處理時既忆,線程可以被用于其他任務(wù);
Java 支持3中網(wǎng)絡(luò)編程模式 :BIO(同步阻塞型)嗦玖、NIO(同步非阻塞型)患雇、AIO(異步非阻塞型);BIO:適用于連接數(shù)目較小且固定的架構(gòu)宇挫;NIO:適用于鏈接數(shù)量多且連接比較短的架構(gòu)苛吱;AIO:適用于連接數(shù)目多且連接比較長的架構(gòu);
BIO 以流的方式處理書局器瘪,NIO 以塊的方式處理書局翠储,塊 IO 的效率比流 IO 高很多;
1.1 Javs NIO 基本介紹
NIO?的三大核心部分部分:Channel(通道)橡疼、Buffer(緩沖區(qū))援所、Selector(選擇器);每個?Channel?都會對應(yīng)一個?Buffer欣除;Selector?對應(yīng)一個線程住拭,一個線程對應(yīng)多個?Channel;多個?Channel?可以注冊到一個?Selector;程序切換到哪個?Channel?由事件?Event?決定滔岳;Selector?會根據(jù)不同的事件瘟檩,在各個通道上切換;Buffer?就是一個內(nèi)存塊澈蟆,底層是一個數(shù)組;數(shù)據(jù)的讀取和寫入通過?Buffer卓研,與 BIO 不同趴俘。BIO 要么是輸入流,要么是輸出流奏赘,不能雙向寥闪,而 NIO 的?Buffer?是雙向的;Channel?也是雙向的磨淌,可以反映底層操作系統(tǒng)的情況疲憋;底層的操作系統(tǒng)通道就是雙向的;
NIO 是面向緩沖區(qū)梁只,或者面向塊編程的缚柳;
NIO 可以做到用一個線程來處理多個操作;
HTTP 2.0 使用了多路復(fù)用技術(shù)搪锣,做到同一個連接并發(fā)處理多個請求秋忙;
1.2 緩沖區(qū) Buffer
緩沖區(qū)本質(zhì)是一個可以讀寫的內(nèi)存塊,可以理解成一個容器對象构舟,該對象提供一組方法灰追,可以更輕松地使用內(nèi)存塊;
緩沖期內(nèi)置了一些機制狗超,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變化情況弹澎;
Channel?提供從文件、網(wǎng)絡(luò)讀取數(shù)據(jù)的通道努咐,但讀取或?qū)懭氲臄?shù)據(jù)必須經(jīng)由?Buffer苦蒿;
Buffer?有四個通用屬性:capacity:容量,即可以容納的最大數(shù)據(jù)量麦撵;在緩存區(qū)創(chuàng)建時被設(shè)定并且不能改變刽肠;limit:表示緩沖區(qū)當(dāng)前的終點,不能對緩沖區(qū)中超過Limit的部分進(jìn)行讀寫(相當(dāng)于哨兵)免胃。而且Limit是可以修改的音五;position:當(dāng)前的讀/寫位置,下一個要被讀或?qū)懙脑氐乃饕嵘常看巫x寫緩沖區(qū)數(shù)據(jù)時都會改變改值躺涝,為下次讀寫作準(zhǔn)備;mark:標(biāo)記;
Buffer?類的通用方法:public abstract class Buffer { //JDK1.4時坚嗜,引入的api public final int capacity()//返回此緩沖區(qū)的容量 public final int position()//返回此緩沖區(qū)的位置 public final Buffer position (int newPositio)//設(shè)置此緩沖區(qū)的位置 public final int limit()//返回此緩沖區(qū)的限制 public final Buffer limit (int newLimit)//設(shè)置此緩沖區(qū)的限制 public final Buffer mark()//在此緩沖區(qū)的位置設(shè)置標(biāo)記 public final Buffer reset()//將此緩沖區(qū)的位置重置為以前標(biāo)記的位置 public final Buffer clear()//清除此緩沖區(qū), 即將各個標(biāo)記恢復(fù)到初始狀態(tài)夯膀,但是數(shù)據(jù)并沒有真正擦除, 后面操作會覆蓋 public final Buffer flip()//反轉(zhuǎn)此緩沖區(qū) public final Buffer rewind()//重繞此緩沖區(qū) public final int remaining()//返回當(dāng)前位置與限制之間的元素數(shù) public final boolean hasRemaining()//告知在當(dāng)前位置和限制之間是否有元素 public abstract boolean isReadOnly();//告知此緩沖區(qū)是否為只讀緩沖區(qū) //JDK1.6時引入的api public abstract boolean hasArray();//告知此緩沖區(qū)是否具有可訪問的底層實現(xiàn)數(shù)組 public abstract Object array();//返回此緩沖區(qū)的底層實現(xiàn)數(shù)組 public abstract int arrayOffset();//返回此緩沖區(qū)的底層實現(xiàn)數(shù)組中第一個緩沖區(qū)元素的偏移量 public abstract boolean isDirect();//告知此緩沖區(qū)是否為直接緩沖區(qū) }
Buffer?類及其子類:
1.2 通道 Channel
BIO 中的 stream 是單向的,例如 FileInputStream 對象只能進(jìn)行讀取數(shù)據(jù)的操作苍蔬,而 NIO 中的通道(Channel)是雙向的诱建,可以讀操作,也可以寫操作;
Channel 在 NIO 中是一個接口 public interface Channel extends Closeable{}碟绑;
常 用 的 Channel 類 有 :?FileChannel?俺猿、 DatagramChannel 、?ServerSocketChannel?和?SocketChannel:
NIO 還支持 通過多個 Buffer(即 Buffer 數(shù)組) 完成讀寫操作格仲,即 Scattering 和 Gathering押袍;
1.3 選擇器 Selector
Java 的 NIO,用非阻塞的 IO 方式凯肋∫瓴眩可以用一個線程缩歪,處理多個的客戶端連接恒序,就會使用到 Selector(選擇器)穿仪;
Selector?能夠檢測多個注冊的通道上是否有事件發(fā)生朗恳;
如果有事件發(fā)生幸海,便獲取事件然后針對每個事件進(jìn)行相應(yīng)的處理防楷。這樣就可以只用一個單線程去管理多個通道辅柴,也就是管理多個連接和請求(IO多路復(fù)用技術(shù))舟舒;
Netty 的 IO 線程 NioEventLoop 聚合了?Selector(選擇器煤伟,也叫多路復(fù)用器)癌佩,可以同時并發(fā)處理成百上千個客戶端連接;
1.4 NIO 非阻塞網(wǎng)絡(luò)編程原理分析
當(dāng)客戶端連接時便锨,會通過?ServerSocketChannel?得到?SocketChannel围辙;
將?socketChannel?注冊到?Selector?上(register 方法);
注冊后返回一個?SelectionKey放案,會和該?Selector?關(guān)聯(lián)(集合)姚建;
Selector?進(jìn)行監(jiān)聽(select 方法),對于有事件發(fā)生的通道吱殉,將對應(yīng)的?SelectionKey?加入到內(nèi)部集合中并返回掸冤;
再通過?SelectionKey?反向獲取?SocketChannel?(方法 channel);
可以通過得到的?channel友雳,完成業(yè)務(wù)處理稿湿;
2. 線程模型概述
目前存在的線程模型有:
傳統(tǒng)阻塞 IO 服務(wù)模型;
Reactor?模式押赊;根據(jù) Reactor 的數(shù)量和處理資源池線程的數(shù)量不同饺藤,有3種典型的實現(xiàn):單 Reactor 單線程;單 Reactor 多線程;主從 Reactor 多線程涕俗;
Netty 主要基于主從 Reactor 多線程模型做了一定的改進(jìn)罗丰,其中主從 Reactor 多線程模型有多個 Reactor;
2.1 傳統(tǒng)阻塞 IO 服務(wù)模型
特點:采用阻塞 IO 模式獲取輸入的數(shù)據(jù)再姑;每個連接都需要獨立的線程完成數(shù)據(jù)的輸入萌抵,業(yè)務(wù)處理,數(shù)據(jù)返回元镀;
問題:當(dāng)并發(fā)數(shù)很大谜嫉,就會創(chuàng)建大量的線程,占用很大系統(tǒng)資源凹联;連接創(chuàng)建后,如果當(dāng)前線程暫時沒有數(shù)據(jù)可讀哆档,該線程會阻塞在 read 操作蔽挠,造成線程資源浪費;
黃色的框表示對象瓜浸, 藍(lán)色的框表示線程澳淑,白色的框表示方法(API);
2.2 Reactor 模式
Reactor 模式又稱:反應(yīng)器模式插佛、分發(fā)者模式(Dispatcher)杠巡、通知者模式(Notifier);
特點:基于 IO 復(fù)用模型:多個連接共用一個阻塞對象雇寇,應(yīng)用程序只需要在一個阻塞對象等待氢拥,無需阻塞等待所有連接。當(dāng)某個連接有新的數(shù)據(jù)可以處理時锨侯,操作系統(tǒng)通知應(yīng)用程序嫩海,線程從阻塞狀態(tài)返回,開始進(jìn)行業(yè)務(wù)處理囚痴;基于線程池復(fù)用線程資源:不必再為每個連接創(chuàng)建線程叁怪,將連接完成后的業(yè)務(wù)處理任務(wù)分配給線程進(jìn)行處理;
說明:通過一個或多個輸入同時傳遞給服務(wù)處理器 ServiceHandler 的模式(基于事件驅(qū)動)深滚;服務(wù)器端程序處理傳入的多個請求奕谭,并將它們同步分派到相應(yīng)的處理線程, 因此 Reactor 模式也叫 Dispatcher 模式痴荐;Reactor 模式使用IO復(fù)用監(jiān)聽事件血柳,收到事件后,分發(fā)給某個線程(進(jìn)程)生兆,這點就是網(wǎng)絡(luò)服務(wù)器高并發(fā)處理關(guān)鍵混驰;
核心組成:Reactor:即:服務(wù)處理器 ServiceHandler:在一個單獨的線程中運行,負(fù)責(zé)監(jiān)聽和分發(fā)事件,分發(fā)給適當(dāng)?shù)奶幚沓绦騺韺?IO 事件做出反應(yīng)栖榨。 它就像公司的電話接線員昆汹,它接聽來自客戶的電話并將線路轉(zhuǎn)移到適當(dāng)?shù)穆?lián)系人;Handlers:即:事件處理器 EventHandler:處理程序執(zhí)行 I/O 事件要完成的實際事件婴栽,類似于客戶想要與之交談的公司中的實際官員满粗。Reactor 通過調(diào)度適當(dāng)?shù)奶幚沓绦騺眄憫?yīng) I/O 事件,處理程序執(zhí)行非阻塞操作愚争;
優(yōu)點:響應(yīng)快映皆,不必為單個同步時間所阻塞,雖然 Reactor 本身依然是同步的轰枝;可以最大程度的避免復(fù)雜的多線程及同步問題捅彻,并且避免了多線程/進(jìn)程的切換開銷;擴展性好鞍陨,可以方便的通過增加 Reactor 實例個數(shù)來充分利用 CPU 資源步淹;復(fù)用性好,Reactor 模型本身與具體事件處理邏輯無關(guān)诚撵,具有很高的復(fù)用性缭裆;
黃色的框表示對象, 藍(lán)色的框表示線程寿烟,白色的框表示方法(API)澈驼;
2.3 單 Reactor 單線程模式
方案說明:Select 是前面 IO 復(fù)用模型介紹的標(biāo)準(zhǔn)網(wǎng)絡(luò)編程 API,可以實現(xiàn)應(yīng)用程序通過一個阻塞對象監(jiān)聽多路連接請求筛武;Reactor?對象通過 select 監(jiān)控客戶端請求事件缝其。收到事件后,通過 dispatch 進(jìn)行分發(fā)徘六;如果建立連接請求氏淑,則?Acceptor?通過 accept 處理連接請求,然后創(chuàng)建一個?Handler?對象處理完成連接后的各種事件硕噩;如果不是連接請求假残,則由?Reactor?分發(fā)調(diào)用連接對應(yīng)的 handler 來處理;Handler?會完成:read → 業(yè)務(wù)處理 → send 的完整業(yè)務(wù)流程炉擅;
優(yōu)點:模型簡單辉懒,沒有多線程、進(jìn)程通信谍失、競爭的問題眶俩,全部都在一個線程中完成;
缺點:性能問題快鱼,只有一個線程颠印,無法完全發(fā)揮多核 CPU 的性能纲岭。Handler 在處理某個連接上的業(yè)務(wù)時,整個進(jìn)程無法處理其他連接事件线罕,很容易導(dǎo)致性能瓶頸止潮;可靠性問題,線程意外終止钞楼,或者進(jìn)入死循環(huán)喇闸,會導(dǎo)致整個系統(tǒng)通信模塊不可用,不能接收和處理外部消息询件,造成節(jié)點故障燃乍;
應(yīng)用場景:客戶端的數(shù)量有限,業(yè)務(wù)處理非惩鹄牛快速刻蟹。如:Redis?在業(yè)務(wù)處理的時間復(fù)雜度 O(1) 的情況;
黃色的框表示對象嘿辟, 藍(lán)色的框表示線程舆瘪,白色的框表示方法(API);
2.4 單 Reactor 多線程模式
方案說明:Reactor?對象通過 select 監(jiān)控客戶端請求事件仓洼,收到事件后,通過 dispatch 進(jìn)行分發(fā)堤舒;如果建立連接請求, 則?Acceptor?通過 accept 處理連接請求色建,然后創(chuàng)建一個?Handler?對象處理完成連接后的各種事件;如果不是連接請求舌缤,則由?Reactor?通過 dispatch 分發(fā)調(diào)用連接對應(yīng)的?Handler?來處理箕戳;Handler?只負(fù)責(zé)響應(yīng)事件,不做具體的業(yè)務(wù)處理国撵,通過 read 讀取數(shù)據(jù)后陵吸,會分發(fā)給后面的?Worker?線程池的某個線程處理業(yè)務(wù);Worker?線程池會分配獨立線程完成真正的業(yè)務(wù)介牙,并將結(jié)果返回給?Handler壮虫;Handler?收到響應(yīng)后,通過 send 將結(jié)果返回給?Client(圖中未標(biāo)出)环础;
優(yōu)點:可以充分的利用多核 cpu 的處理能力囚似;
缺點:多線程數(shù)據(jù)共享和訪問比較復(fù)雜;Reactor 處理所有的事件的監(jiān)聽和響應(yīng)线得,在單線程運行饶唤、高并發(fā)場景容易出現(xiàn)性能瓶頸;
黃色的框表示
對象贯钩, 藍(lán)色的框表示線程募狂,白色的框表示方法(API)办素;
2.5 主從 Reactor 多線程模式
方案說明:Reactor?主線程?MainReactor?對象通過 select 監(jiān)聽連接事件。收到事件后祸穷,通過?Acceptor?處理連接事件性穿;當(dāng)?Acceptor?處理連接事件后,MainReactor?將連接分配給?SubReactor(多個)粱哼;SubReactor?將連接加入到連接隊列進(jìn)行監(jiān)聽季二,并創(chuàng)建?Handler?進(jìn)行各種事件處理;當(dāng)有新事件發(fā)生時揭措,?Subreactor?就會調(diào)用對應(yīng)的?Handler?處理胯舷;Handler?先 read 讀取數(shù)據(jù),然后分發(fā)給后面的?Worker?線程池處理绊含;Worker?線程池分配獨立的?Worker?線程進(jìn)行業(yè)務(wù)處理桑嘶,并返回結(jié)果;Handler?收到響應(yīng)的結(jié)果后躬充,再通過 send 將結(jié)果返回給?Client逃顶;Reactor?主線程可以對應(yīng)多個?Reactor?子線程,即?MainRecator?可以關(guān)聯(lián)多個?SubReactor充甚;
優(yōu)點:父線程與子線程的數(shù)據(jù)交互簡單職責(zé)明確以政,父線程只需要接收新連接,子線程完成后續(xù)的業(yè)務(wù)處理伴找;父線程與子線程的數(shù)據(jù)交互簡單盈蛮,Reactor 主線程只需要把新連接傳給子線程,子線程無需返回數(shù)據(jù)技矮;
缺點:編程復(fù)雜度較高抖誉;
應(yīng)用場景:這種模型在許多項目中廣泛使用,包括 Nginx 主從 Reactor 多進(jìn)程模型衰倦,Memcached 主從多線程袒炉,Netty 主從多線程模型的支持;
黃色的框表示對象樊零, 藍(lán)色的框表示線程我磁,白色的框表示方法(API);
2.6 Netty 模型
Netty 主要基于主從 Reactors 多線程模型做了一定的改進(jìn)驻襟,其中主從?Reactor?多線程模型有多個?Reactor十性;
方案說明:Netty 抽象出兩組線程池:?BossGroup?專門負(fù)責(zé)接收客戶端的連接、WorkerGroup?專門負(fù)責(zé)網(wǎng)絡(luò)的讀寫塑悼;BossGroup?和?WorkerGroup類型都是?NioEventLoopGroup?劲适。NioEventLoopGroup?相當(dāng)于一個事件循環(huán)組,這個組中含有多個事件循環(huán)厢蒜,每一個事件循環(huán)是?NioEventLoop霞势;NioEventLoop?表示一個不斷循環(huán)的執(zhí)行處理任務(wù)的線程烹植, 每個?NioEventLoop?都有一個?Selector,用于監(jiān)聽綁定在其上的 socket 的網(wǎng)絡(luò)通訊愕贡;每個?Boss NioEventLoop?循環(huán)執(zhí)行的步驟有 3 步:輪詢 accept 事件草雕;處理 accept 事件,與?Client?建立連接固以,生成?NioScocketChannel墩虹,并將其注冊到某個?Worker NIOEventLoop?上 的?Selector;處理任務(wù)隊列的其他任務(wù) 憨琳, 即 runAllTasks诫钓;每個?Worker NIOEventLoop?循環(huán)執(zhí)行的步驟有 3 步:輪詢 read、write 事件篙螟;在對應(yīng)?NioScocketChannel上進(jìn)行 處理 IO 事件菌湃, 即 read、write 事件遍略;處理任務(wù)隊列的其他任務(wù) 惧所, 即 runAllTasks;每個?WorkerNIOEventLoop?處理業(yè)務(wù)時绪杏,會使用 pipeline(管道)下愈,pipeline 中包含了 channel,即通過 pipeline 可以獲取到對應(yīng)通道蕾久,管道中維護(hù)了很多的處理器势似;