網(wǎng)絡(luò)編程之認(rèn)識Netty

作者:xiaoxiyuan 文章內(nèi)容輸出來源:拉勾教育Java高薪訓(xùn)練營

本文主要內(nèi)容包括:
Netty簡介、Netty 高性能(零拷貝和支持高性能序列化協(xié)議等)、Netty線程模型判没、Netty粘包與拆包旧巾、Netty核心組件利虫、Netty版自定義RPC案例實現(xiàn)、Netty源碼剖析

1、Netty簡介

1.1 Netty是什么?

Netty官網(wǎng)https://netty.io/

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.

Netty介紹:Netty 是一個提供異步的盖袭、基于事件驅(qū)動的網(wǎng)絡(luò)編程框架,用以快速開發(fā)高性能的網(wǎng)絡(luò)服務(wù)端和客戶端。
說明:
異步:當(dāng)一個異步進程調(diào)用發(fā)出之后鳄虱,調(diào)用者不會立刻得到結(jié)果弟塞。而是在調(diào)用發(fā)出之后,被調(diào)用者通過狀態(tài)拙已、通知來通知調(diào)用者决记,或者通過回調(diào)函數(shù)來處理這個調(diào)用。
事件驅(qū)動:發(fā)生事件倍踪,主線程把事件放入事件隊列系宫,在另外線程不斷循環(huán)消費事件列表中的事件,調(diào)用事件對應(yīng)的處理邏輯處理事件惭适,即事件發(fā)生時才來處理事件笙瑟。事件驅(qū)動方式也被稱為消息通知方式楼镐,相對于輪詢方式(線程不斷輪詢訪問相關(guān)事件發(fā)生源有沒有發(fā)生事件癞志,有發(fā)生事件就調(diào)用事件處理邏輯)表現(xiàn)為可擴展性好,在分布式的異步架構(gòu)中框产,事件處理器之間高度解耦凄杯,可以方便擴展事件處理邏輯,其次表現(xiàn)出高性能秉宿,基于隊列暫存事件戒突,能方便并行異步處理事件。

1.2 Netty應(yīng)用場景

(1)消息服務(wù)器描睦,例如消息中間件RocketMQ底層用Netty作為通信框架膊存;
(2)即時通訊(IM,Instant Messaging)忱叭,例如在彈幕系統(tǒng)隔崎、游戲系統(tǒng)、直播群聊等當(dāng)中即時發(fā)送和接收消息韵丑;
(3)RPC框架爵卒,例如阿里巴巴開源RPC框架(Dubbo) 也是使用Netty作為底層通信框架的;
(4)其他應(yīng)用撵彻,例如ElasticSearch搜索引擎框架(github搜索)钓株、Hadopp子項目Avro項目等,都是使用Netty作為底層通信框架的陌僵;

1.3 Netty組成

components.png

Core(核心模塊):可擴展事件模型轴合、通用通信API、支持零拷貝的ByteBuf緩沖對象;
Protocol Support(協(xié)議支持模塊):HTTP碗短、Protobuf受葛、二進制、文本、WebSocket等一系列常見協(xié)議都支持奔坟。還支持通過實行編碼解碼邏輯來實現(xiàn)自定義協(xié)議;
Transport Services(傳輸服務(wù)支持模塊):BIO和NIO的socket服務(wù)携栋、HTTP隧道服務(wù)等;

1.4 Netty解決的問題

Netty可以解決原生NIO存在的問題。
原生NIO存在的問題:
(1)NIO的類庫和 API繁雜咳秉,使用麻煩婉支。比如需要熟練掌握 Selector、ServerSocketChannel澜建、SocketChannel向挖、ByteBuffer 等。
(2)可靠性不強炕舵,開發(fā)工作量和難度都非常大何之。比如客戶端面臨斷連重連、網(wǎng)絡(luò)閃斷咽筋、網(wǎng)絡(luò)擁塞和異常流的處理等溶推。
(3)JDK NIO 的 Bug:比如 Epoll Bug,它會導(dǎo)致 Selector 空輪詢奸攻,最終導(dǎo)致 CPU 100%蒜危。

2、Netty 高性能表現(xiàn)(優(yōu)點)

(1)適應(yīng)各種傳輸協(xié)議:對各種傳輸協(xié)議提供統(tǒng)一的 API睹耐;
(2)IO 線程模型:同步非阻塞辐赞,用最少的資源做更多的事;
(3)更好的吞吐量硝训,更低的等待延遲响委;
(4)串形化處理讀寫:避免使用鎖帶來的性能開銷;
(5)內(nèi)存零拷貝:盡量減少不必要的內(nèi)存拷貝窖梁,實現(xiàn)了更高效率的傳輸赘风;
(6)內(nèi)存池設(shè)計:申請的內(nèi)存可以重用,主要指直接內(nèi)存窄绒。內(nèi)部實現(xiàn)是用一顆二叉查找樹管理內(nèi)存分配情況贝次;
(7)高性能序列化協(xié)議:支持 protobuf 等高性能序列化協(xié)議;
擴展說明:
Netty零拷貝
【1】網(wǎng)絡(luò)編程中零拷貝的理解
零拷貝是服務(wù)器網(wǎng)絡(luò)編程的關(guān)鍵彰导,任何性能優(yōu)化都離不開蛔翅。零拷貝從操作系統(tǒng)角度來說,是沒有cpu 拷貝位谋,在 Java 中山析,常用的零拷貝優(yōu)化技術(shù)有 mmapsendFile
mmap優(yōu)化:指通過內(nèi)存映射掏父,將文件映射到內(nèi)核緩沖區(qū)笋轨,同時用戶空間可以共享內(nèi)核空間的數(shù)據(jù)。這樣,在進行網(wǎng)絡(luò)傳輸時爵政,就可以減少內(nèi)核空間到用戶控件的拷貝次數(shù)仅讽。
sendFile優(yōu)化:Linux系統(tǒng)版本中提供了 sendFile 函數(shù),其基本原理是:數(shù)據(jù)根本不經(jīng)過用戶態(tài)钾挟,直接從內(nèi)核緩沖區(qū)進入到 Socket Buffer洁灵,同時由于和用戶態(tài)完全無關(guān),就減少了一次上下文切換掺出。
mmap 和 sendFile 的區(qū)別:
(1)mmap 適合小數(shù)據(jù)量讀寫徽千,sendFile 適合大文件傳輸。
(2)mmap 需要 4 次上下文切換汤锨,3 次數(shù)據(jù)拷貝双抽;sendFile 需要 3 次上下文切換,最少 2 次數(shù)據(jù)拷貝闲礼。
(3)sendFile 可以利用 DMA 方式牍汹,減少 CPU 拷貝,mmap 則不能(必須從內(nèi)核拷貝到 Socket 緩沖區(qū))位仁。
在這個選擇上:RocketMQ 在消費消息時柑贞,使用了 mmap;kafka 使用了 sendFile聂抢。
【2】Netty中零拷貝理解
(1)Netty 的接收和發(fā)送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接內(nèi)存進行 Socket 讀寫棠众,不需要進行字節(jié)緩沖區(qū)的二次拷貝琳疏。如果使用傳統(tǒng)的堆內(nèi)存(HEAP BUFFERS)進行 Socket讀寫,JVM會將堆內(nèi)存 Buffer拷貝一份到直接內(nèi)存中闸拿,然后才寫入 Socket 中空盼。相比于堆外直接內(nèi)存,消息在發(fā)送過程中多了一次緩沖區(qū)的內(nèi)存拷貝新荤。
(2)Netty 提供了組合 Buffer 對象揽趾,可以聚合多個 ByteBuffer 對象,用戶可以像操作一個 Buffer 那樣方便的對組合 Buffer 進行操作苛骨,避免了傳統(tǒng)通過內(nèi)存拷貝的方式將幾個小 Buffer 合并成一個大的 Buffer篱瞎。
(3)Netty 的文件傳輸采用了 transferTo 方法,它可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel痒芝,避免了傳統(tǒng)通過循環(huán) write 方式導(dǎo)致的內(nèi)存拷貝問題俐筋。

Netty支持的高性能序列化協(xié)議
【1】序列化和反序列化
網(wǎng)絡(luò)應(yīng)用程序中,因為數(shù)據(jù)在網(wǎng)絡(luò)中傳輸?shù)亩际嵌M制字節(jié)碼數(shù)據(jù)严衬,在發(fā)送數(shù)據(jù)時就需要編碼澄者,接收數(shù)據(jù)時就需要解碼 。
序列化(編碼)是將對象序列化為二進制形式(字節(jié)數(shù)組),主要用于網(wǎng)絡(luò)傳輸粱挡、數(shù)據(jù)持久化等赠幕;
反序列化(解碼)則是將從網(wǎng)絡(luò)、磁盤等讀取的字節(jié)數(shù)組還原成原始對象询筏,主要用于網(wǎng)絡(luò)傳輸對象的解碼劣坊,以便完成遠程調(diào)用。
影響序列化性能的關(guān)鍵因素:序列化后的碼流大星簟(網(wǎng)絡(luò)帶寬的占用)局冰、序列化的性能(CPU資源占用);是否支持跨語言(異構(gòu)系統(tǒng)的對接和開發(fā)語言切換)灌危。
【2】幾種常見的序列化協(xié)議
Java默認(rèn)提供的序列化:無法跨語言康二、序列化后的碼流太大、序列化的性能差勇蝙;
XML:優(yōu)點:人機可讀性好沫勿,可指定元素或特性的名稱。缺點:序列化數(shù)據(jù)只包含數(shù)據(jù)本身以及類的結(jié)構(gòu)味混,不包括類型標(biāo)識和程序集信息产雹;只能序列化公共屬性和字段;不能序列化方法翁锡;文件龐大蔓挖,文件格式復(fù)雜,傳輸占帶寬馆衔。適用場景:當(dāng)做配置文件存儲數(shù)據(jù)瘟判,實時數(shù)據(jù)轉(zhuǎn)換;
JSON角溃,是一種輕量級的數(shù)據(jù)交換格式拷获,優(yōu)點:兼容性高、數(shù)據(jù)格式比較簡單减细,易于讀寫匆瓜、序列化后數(shù)據(jù)較小,可擴展性好未蝌,兼容性好驮吱、與XML相比,其協(xié)議比較簡單树埠,解析速度比較快糠馆。缺點:數(shù)據(jù)的描述性比XML差、不適合性能要求為ms級別的情況怎憋、額外空間開銷比較大又碌。適用場景(可替代XML):跨防火墻訪問九昧、可調(diào)式性要求高、基于Web browser的Ajax請求毕匀、傳輸數(shù)據(jù)量相對小铸鹰,實時性要求相對低(例如秒級別)的服務(wù);
Fastjson皂岔,采用一種“假定有序快速匹配”的算法蹋笼。優(yōu)點:接口簡單易用、目前java語言中最快的json庫躁垛。缺點:過于注重快剖毯,而偏離了“標(biāo)準(zhǔn)”及功能性、代碼質(zhì)量不高教馆,文檔不全逊谋。適用場景:協(xié)議交互、Web輸出土铺、Android客戶端胶滋;
Thrift,不僅是序列化協(xié)議悲敷,還是一個RPC框架究恤。優(yōu)點:序列化后的體積小, 速度快、支持多種語言和豐富的數(shù)據(jù)類型后德、對于數(shù)據(jù)字段的增刪具有較強的兼容性部宿、支持二進制壓縮編碼。缺點:使用者較少探遵、跨防火墻訪問時窟赏,不安全、不具有可讀性箱季,調(diào)試代碼時相對困難、不能與其他傳輸層協(xié)議共同使用(例如HTTP)棍掐、無法支持向持久層直接讀寫數(shù)據(jù)藏雏,即不適合做數(shù)據(jù)持久化序列化協(xié)議。適用場景:分布式系統(tǒng)的RPC解決方案作煌;
Avro掘殴,Hadoop的一個子項目,解決了JSON的冗長和沒有IDL的問題粟誓。優(yōu)點:支持豐富的數(shù)據(jù)類型奏寨、簡單的動態(tài)語言結(jié)合功能、具有自我描述屬性鹰服、提高了數(shù)據(jù)解析速度病瞳、快速可壓縮的二進制數(shù)據(jù)形式揽咕、可以實現(xiàn)遠程過程調(diào)用RPC、支持跨編程語言實現(xiàn)套菜。缺點:對于習(xí)慣于靜態(tài)類型語言的用戶不直觀亲善。適用場景:在Hadoop中做Hive、Pig和MapReduce的持久化數(shù)據(jù)格式逗柴。
Protobuf蛹头,將數(shù)據(jù)結(jié)構(gòu)以.proto文件進行描述,通過代碼生成工具可以生成對應(yīng)數(shù)據(jù)結(jié)構(gòu)的POJO對象和Protobuf相關(guān)的方法和屬性戏溺。優(yōu)點:序列化后碼流小渣蜗,性能高、結(jié)構(gòu)化數(shù)據(jù)存儲格式(XML JSON等)旷祸、通過標(biāo)識字段的順序耕拷,可以實現(xiàn)協(xié)議的前向兼容、結(jié)構(gòu)化的文檔更容易管理和維護肋僧。缺點:需要依賴于工具生成代碼斑胜、支持的語言相對較少,官方只支持Java 嫌吠、C++ 止潘、python。適用場景:對性能要求高的RPC調(diào)用辫诅、具有良好的跨防火墻的訪問屬性凭戴、適合應(yīng)用層對象的持久化;
【3】Google 的 Protobuf
Protobuf 是 Google 發(fā)布的開源項目炕矮,全稱 Google Protocol Buffers么夫,是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化肤视,或者說序列化档痪。它很適合做數(shù)據(jù)存儲或 RPC[遠程過程調(diào)用 remote procedure call ] 數(shù)據(jù)交換格式 。目前很多公司 http+json 向 tcp+protobuf轉(zhuǎn)變邢滑。Protobuf 是以 message 的方式來管理數(shù)據(jù)腐螟,支持跨平臺、跨語言困后,即客戶端和服務(wù)器端可以是不同的語言編寫的(支持目前絕大多數(shù)語言乐纸,例如 C++、C#摇予、Java汽绢、python 等),Protobuf具有高可靠性侧戴,使用 protobuf 編譯器能自動生成代碼宁昭,Protobuf 是將類的定義使用.proto 文件進行描述跌宛。在idea 中編寫 .proto 文件時,會自動提示是否下載 .ptotot編寫插件. 可以讓語法高亮久窟。然后通過 protoc.exe 編譯器根據(jù).proto 自動生成.java 文件秩冈。
protocol-buffers的參考文檔地址 : https://developers.google.com/protocol-buffers/docs/proto

3、Netty線程模型

傳統(tǒng)的線程模型有:

單線程模型:
image-20200606155356691.png

線程池模型:
image-20200606155448556.png

Netty線程模型:


image-20200606155530496.png

Netty 抽象出兩組線程池斥扛, BossGroup 專門負(fù)責(zé)接收客戶端連接入问, WorkerGroup 專門負(fù)責(zé)網(wǎng)絡(luò)讀寫操作。
NioEventLoop 表示一個不斷循環(huán)執(zhí)行處理任務(wù)的線程稀颁, 每個 NioEventLoop 都有一個 selector芬失, 用于監(jiān)聽綁定在其上的 socket 網(wǎng)絡(luò)通道。 NioEventLoop 內(nèi)部采用串行化設(shè)計匾灶, 從消息的讀取-->解碼-->處理-->編碼-->發(fā)送棱烂, 始終由 IO 線 程 NioEventLoop 負(fù)責(zé)。
說明:
(1)BossGroup 和 WorkerGroup 類型都是NioEventLoopGroup阶女,NioEventLoopGroup 相當(dāng)于一個事件循環(huán)組, 這個組中含有多個事件循環(huán) 颊糜,每一個事件循環(huán)是 NioEventLoop;
(2)NioEventLoopGroup 可以有多個線程, 即可以含有多個NioEventLoop秃踩;
(3)每個Boss NioEventLoop 循環(huán)執(zhí)行的步驟有3步:
? 1.輪詢accept 事件衬鱼;
? 2.處理accept 事件 , 與client建立連接 , 生成NioScocketChannel , 并將其注冊到某個worker NIOEventLoop 上的 selector;
? 3.處理任務(wù)隊列的任務(wù) 憔杨;
(4)每個 Worker NIOEventLoop 循環(huán)執(zhí)行步驟有3步:
? 1.輪詢read, write 事件
? 2.處理i/o事件鸟赫, 即read , write 事件,在對應(yīng)NioScocketChannel 處理
? 3.處理任務(wù)隊列的任務(wù) 消别;
(5)每個Worker NIOEventLoop 處理業(yè)務(wù)時抛蚤,會使用pipeline(管道), pipeline 中包含了 channel , 即通過pipeline 可以獲取到對應(yīng)通道, 管道中維護了很多的 處理器。

4寻狂、Netty粘包與拆包

4.1 TCP 粘包和拆包

TCP是面向連接的岁经,面向流的,提供高可靠性服務(wù)蛇券。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket蒿偎,因此,發(fā)送端為了將多個數(shù)據(jù)包更有效的發(fā)給接收端怀读,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)骑脱,合并成一個大的數(shù)據(jù)塊菜枷,然后進行封包。這樣做雖然提高了效率叁丧,但因為面向流的通信是無消息保護邊界的啤誊,這樣接收端就難于分辨出完整的數(shù)據(jù)包了岳瞭,可能發(fā)送的原來完整的包之間出現(xiàn)了粘合或完整的包接收到后就不完整(拆解),這種情況就是TCP粘包和拆包問題蚊锹。

4.2 TCP粘包拆包圖解

image-20200615102623300.png

假設(shè)客戶端分別發(fā)送了兩個數(shù)據(jù)包D1和D2給服務(wù)端瞳筏,由于服務(wù)端一次讀取到字節(jié)數(shù)是不確定的,故可能存在以下四種情況:
● 服務(wù)端分兩次讀取到了兩個獨立的數(shù)據(jù)包牡昆,分別是D1和D2姚炕,沒有粘包和拆包;
● 服務(wù)端一次接受到了兩個數(shù)據(jù)包丢烘,D1和D2粘合在一起柱宦,稱之為TCP粘包;
● 服務(wù)端分兩次讀取到了數(shù)據(jù)包播瞳,第一次讀取到了完整的D1包和D2包的部分內(nèi)容掸刊,第二次讀取到了D2包的剩余內(nèi)容,這稱之為TCP拆包赢乓;
● 服務(wù)端分兩次讀取到了數(shù)據(jù)包忧侧,第一次讀取到了D1包的部分內(nèi)容D1_1,第二次讀取到了D1包的剩余部分內(nèi)容D1_2和完整的D2包牌芋。

4.3 TCP粘包拆包現(xiàn)象實例及解決方案

在編寫Netty 程序時蚓炬,如果沒有做處理,就會發(fā)生粘包和拆包的問題姜贡;
TCP粘包和拆包解決方案
(1)使用自定義序列化協(xié)議編解碼器來解決
(2)關(guān)鍵就是要解決服務(wù)器端每次讀取數(shù)據(jù)長度的問題, 這個問題解決试吁,就不會出現(xiàn)服務(wù)器多讀或少讀數(shù)據(jù)的問題,從而避免的TCP 粘包楼咳、拆包 熄捍。
項目地址:https://gitee.com/tudedong/netty-tcp.git

5、Netty核心組件

【ChannelHandler 及其實現(xiàn)類】
ChannelHandler 接口定義了許多事件處理的方法母怜, 我們可以通過重寫這些方法去實現(xiàn)具體的業(yè)務(wù)邏輯余耽;
我們經(jīng)常需要自定義一個 Handler 類去繼承 ChannelInboundHandlerAdapter, 然后通過重寫相應(yīng)方法實現(xiàn)業(yè)
務(wù)邏輯苹熏, 我們接下來看看一般都需要重寫哪些方法:

- public void channelActive(ChannelHandlerContext ctx)碟贾, 通道就緒事件
- public void channelRead(ChannelHandlerContext ctx, Object msg), 通道讀取數(shù)據(jù)事件
- public void channelReadComplete(ChannelHandlerContext ctx) 轨域, 數(shù)據(jù)讀取完畢事件
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)袱耽, 通道發(fā)生異常事件

Pipeline ChannelPipeline
ChannelPipeline 是一個 Handler 的集合,它負(fù)責(zé)處理和攔截 inbound 或者 outbound 的事件和操作干发,相當(dāng)于一個貫穿 Netty 的鏈朱巨。(可以這樣理解:ChannelPipeline 是保存 ChannelHandler 的 List,用于處理或攔截Channel 的入站事件和出站操作)枉长;
ChannelPipeline 實現(xiàn)了一種高級形式的攔截過濾器模式冀续,使用戶可以完全控制事件的處理方式琼讽,以及 Channel 中各個的 ChannelHandler 如何交互;
在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline 與之對應(yīng)洪唐,組成關(guān)系圖如下:

image-20200606171625810.png

說明:
一個 Channel 包含了一個 ChannelPipeline钻蹬,而 ChannelPipeline 中又維護了一個由 ChannelHandlerContext 組成的雙向鏈表,并且每個 ChannelHandlerContext 中又關(guān)聯(lián)著一個 ChannelHandler凭需,入站事件和出站事件在一個雙向鏈表中问欠,入站事件會從鏈表 head 往后傳遞到最后一個入站的 handler,出站事件會從鏈表 tail 往前傳遞到最前一個出站的 handler功炮,兩種類型的 handler 互不干擾溅潜。
常用方法有:

- ChannelPipeline addFirst(ChannelHandler... handlers), 把一個業(yè)務(wù)處理類(handler) 添加到鏈中的第一個位置
- ChannelPipeline addLast(ChannelHandler... handlers)薪伏, 把一個業(yè)務(wù)處理類(handler) 添加到鏈中的最后一個位置

【ChannelHandlerContext】
ChannelHandlerContext 是 事 件 處 理 器 上 下 文 對 象 滚澜, Pipeline 鏈 中 的 實 際 處 理 節(jié) 點 。 每 個 處 理 節(jié) 點ChannelHandlerContext 中 包 含 一 個 具 體 的 事 件 處 理 器 ChannelHandler 嫁怀, 同 時
ChannelHandlerContext 中也綁定了對應(yīng)的 pipeline 和 Channel 的信息设捐,方便對 ChannelHandler 進行調(diào)用。
常用方法如下所示:

- ChannelFuture close()塘淑, 關(guān)閉通道
- ChannelOutboundInvoker flush()萝招, 刷新
- ChannelFuture writeAndFlush(Object msg) , 將 數(shù) 據(jù) 寫 到 ChannelPipeline 中 當(dāng) 前
- ChannelHandler 的下一個 ChannelHandler 開始處理(出站)

ChannelOption
Netty 在創(chuàng)建 Channel 實例后,一般都需要設(shè)置 ChannelOption 參數(shù)存捺。ChannelOption 參數(shù)如下:

ChannelOption.SO_BACKLOG
對應(yīng) TCP/IP 協(xié)議 listen 函數(shù)中的 backlog 參數(shù)槐沼,用來初始化服務(wù)器可連接隊列大小。服務(wù)端處理客戶端連接請求是順序處理的捌治,所以同一時間只能處理一個客戶端連接岗钩。多個客戶端來的時候,服務(wù)端將不能處理的客戶端連接請求放在隊列中等待處理肖油,backlog 參數(shù)指定了隊列的大小兼吓。

ChannelOption.SO_KEEPALIVE
一直保持連接活動狀態(tài)

【EventLoopGroup 和其實現(xiàn)類 NioEventLoopGroup】
EventLoopGroup 是一組 EventLoop 的抽象, Netty 為了更好的利用多核 CPU 資源森枪, 一般會有多個 EventLoop
同時工作视搏, 每個 EventLoop 維護著一個 Selector 實例。 EventLoopGroup 提供 next 接口县袱, 可以從組里面按照一定規(guī)則獲取其中一個 EventLoop 來處理任務(wù)浑娜。 在 Netty 服務(wù)器端編程中, 我們一般都需要提供兩個
EventLoopGroup式散, 例如: BossEventLoopGroup 和 WorkerEventLoopGroup棚愤。
常用方法:

- public NioEventLoopGroup(), 構(gòu)造方法
- public Future<?> shutdownGracefully(), 斷開連接宛畦, 關(guān)閉線程

【Selector】
Netty 基于 Selector 對象實現(xiàn) I/O 多路復(fù)用,通過 Selector 一個線程可以監(jiān)聽多個連接的 Channel 事件揍移,當(dāng)向一個 Selector 中注冊 Channel 后次和,Selector 內(nèi)部的機制就可以自動不斷地查詢(select) 這些注冊的 Channel 是否有已就緒的 I/O 事件(例如可讀,可寫那伐,網(wǎng)絡(luò)連接完成等)踏施,這樣程序就可以很簡單地使用一個線程高效地管理多個 Channel。

【Channel 和Future罕邀、ChannelFuture】
在 Netty 中所有的 I/O 操作都是異步的畅形, I/O 的調(diào)用會直接返回, 調(diào)用者并不能立刻獲得結(jié)果诉探, 但是可以過一會等它執(zhí)行完成或者直接注冊一個監(jiān)聽日熬,具體的實現(xiàn)就是通過 Future 和 ChannelFuture,它們可以注冊一個監(jiān)聽肾胯,當(dāng)操作執(zhí)行成功或失敗時監(jiān)聽會自動觸發(fā)注冊的監(jiān)聽事件;
Channel 是Netty 網(wǎng)絡(luò)通信的組件竖席,能夠用于執(zhí)行網(wǎng)絡(luò) I/O 操作,通過Channel 可獲得當(dāng)前網(wǎng)絡(luò)連接的通道的狀態(tài)和網(wǎng)絡(luò)連接的配置參數(shù) (例如接收緩沖區(qū)大芯炊恰)毕荐,Channel 提供異步的網(wǎng)絡(luò) I/O 操作(如建立連接,讀寫艳馒,綁定端口)憎亚,異步調(diào)用意味著任何 I/O 調(diào)用都將立即返回,并且不保證在調(diào)用結(jié)束時所請求的 I/O 操作已完成弄慰,調(diào)用立即返回一個 ChannelFuture 實例第美,通過注冊監(jiān)聽器到 ChannelFuture 上,可以 I/O 操作成功曹动、失敗或取消時回調(diào)通知調(diào)用方斋日。
不同協(xié)議、不同的阻塞類型的連接都有不同的 Channel 類型與之對應(yīng)墓陈,常用的 Channel 類型:

NioSocketChannel恶守,異步的客戶端 TCP Socket 連接。
NioServerSocketChannel贡必,異步的服務(wù)器端 TCP Socket 連接兔港。
NioDatagramChannel,異步的 UDP 連接仔拟。
NioSctpChannel衫樊,異步的客戶端 Sctp 連接。
NioSctpServerChannel,異步的 Sctp 服務(wù)器端連接科侈,這些通道涵蓋了 UDP 和 TCP 網(wǎng)絡(luò) IO 以及文件 IO载佳。

ChannelFuture表示 Channel 中異步 I/O 操作的結(jié)果;
常用方法如下所示:

- Channel channel()臀栈, 返回當(dāng)前正在進行 IO 操作的通道
- ChannelFuture sync()蔫慧, 等待異步操作執(zhí)行完畢

【Bootstrap 和 ServerBootstrap】
Bootstrap 是“引導(dǎo)”的意思,Bootstrap類 是 Netty 中的客戶端動引導(dǎo)類权薯, 通過它可以完成客戶端的各種配置姑躲;ServerBootstrap類 是 Netty 中的服務(wù)器端啟動引導(dǎo)類,通過它可以完成服務(wù)器端的各種配置盟蚣; 它們的主要作用是配置整個 Netty 程序黍析,串聯(lián)各個組件;
常用方法如下所示:

- public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)屎开,該方法用于服務(wù)器端阐枣, 用來設(shè)置兩個 EventLoop
- public B group(EventLoopGroup group) , 該方法用于客戶端牍戚, 用來設(shè)置一個 EventLoop
- public B channel(Class<? extends C> channelClass)侮繁, 該方法用來設(shè)置一個服務(wù)器端的通道實現(xiàn)
- public <T> B option(ChannelOption<T> option, T value), 用來給 ServerChannel 添加配置
- public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value)如孝, 用來給接收到的通道添加配置
- public ServerBootstrap childHandler(ChannelHandler childHandler)宪哩, 該方法用來設(shè)置業(yè)務(wù)處理類(自定義的 handler)
- public ChannelFuture bind(int inetPort) , 該方法用于服務(wù)器端第晰, 用來設(shè)置占用的端口號
- public ChannelFuture connect(String inetHost, int inetPort) 該方法用于客戶端锁孟,用來連接服務(wù)端

總結(jié):
組件間的關(guān)系用服務(wù)的啟動流程和請求的處理流程來說明下:
啟動流程:無論是服務(wù)端還是客戶端,先創(chuàng)建EventLoopGroup線程池組茁瘦,用來接收請求或處理請求品抽,后創(chuàng)建Bootstrap啟動器輔助引導(dǎo)類,接著對Bootstrap進行初始化設(shè)置甜熔,包括創(chuàng)建SocketChannel即TCP連接Socket圆恤、創(chuàng)建ChannelPipeline、加入編解碼器腔稀、加入ChannelHandlerContext 事 件 處 理 器 上 下 文 對 象盆昙、加入ChannelHandler事件處理器等, Bootstrap初始化設(shè)置完成后焊虏,綁定或連接服務(wù)地址淡喜,實現(xiàn)監(jiān)聽等;
請求處理流程:輪詢監(jiān)聽過程诵闭,當(dāng)一個請求過來時炼团,經(jīng)過SocketChannel澎嚣,進入ChannelPipeline,然后把其中的每個ChannelHandler的邏輯都執(zhí)行下瘟芝,完成服務(wù)端和客戶端間的交互易桃。

6、Netty版自定義RPC案例實現(xiàn)

image-20200607135717933-1592273445777.png

案例項目地址:https://gitee.com/tudedong/zdy-netty-rpc.git

7模狭、Netty源碼剖析

Netty源碼剖析將從Netty啟動過程颈抚、Netty接受請求、三大核心組件ChannelPipeline 和ChannelHandler及ChannelHandlerContex的創(chuàng)建嚼鹉、ChannelPipeline調(diào)度handler、Netty心跳(heartbeat)機制驱富、Netty 核心組件 EventLoop和handler中加入線程池和Context 中添加線程池七個方面進行說明锚赤。

7.1 Netty啟動過程源碼剖析

(1)創(chuàng)建2個 EventLoopGroup 線程池數(shù)組。數(shù)組默認(rèn)大小CPU*2褐鸥,方便選擇線程池時提高性能线脚;
(2)BootStrap 將 boss 設(shè)置為 group屬性,將 worker 設(shè)置為 childer 屬性叫榕;
(3)通過 bind 方法啟動浑侥,內(nèi)部重要方法為 initAndRegister 和 dobind 方法;
(4)initAndRegister 方法會反射創(chuàng)建 NioServerSocketChannel 及其相關(guān)的 NIO 的對象晰绎, pipeline 寓落, unsafe,同時也為 pipeline 初始化了 head 節(jié)點和 tail 節(jié)點荞下;
(5)在register0 方法成功以后調(diào)用在 dobind 方法中調(diào)用 doBind0 方法伶选,該方法會 調(diào)用 NioServerSocketChannel 的 doBind 方法對 JDK 的 channel 和端口進行綁定,完成 Netty 服務(wù)器的所有啟動尖昏,并開始監(jiān)聽連接事件仰税。

7.2 Netty接受請求過程源碼剖析

(1)接受連接,服務(wù)器輪詢 Accept 事件抽诉,獲取事件后調(diào)用 unsafe 的 read 方法陨簇,這個 unsafe 是 ServerSocket 的內(nèi)部類,該方法內(nèi)部由兩部分組成迹淌;
(2)創(chuàng)建一個新的NioSocketChannel河绽,即doReadMessages 用于創(chuàng)建 NioSocketChannel 對象,該對象包裝 JDK 的 Nio Channel 客戶端巍沙,該方法會像創(chuàng)建 ServerSocketChanel 類似創(chuàng)建相關(guān)的 pipeline 葵姥, unsafe,config句携;
(3)注冊到一個 worker EventLoop 上并 注冊selecot Read 事件榔幸,隨后執(zhí)行 pipeline.fireChannelRead 方法,并將自己綁定到一個 chooser 選擇器選擇的 workerGroup 中的一個 EventLoop。并且注冊一個0削咆,表示注冊成功牍疏,但并沒有注冊讀(1)事件。

7.3 三大核心組件ChannelPipeline 拨齐、 ChannelHandler 和ChannelHandlerContext創(chuàng)建源碼剖析

(1)每當(dāng)創(chuàng)建 ChannelSocket 的時候都會創(chuàng)建一個綁定的 pipeline鳞陨,一對一的關(guān)系,創(chuàng)建 pipeline 的時候也會創(chuàng)建 tail 節(jié)點和 head 節(jié)點瞻惋,形成最初的鏈表厦滤;
(2)在調(diào)用 pipeline 的 addLast 方法的時候,會根據(jù)給定的 handler 創(chuàng)建一個 Context歼狼,然后將這個 Context 插入到鏈表的尾端(tail 前面)掏导;
(3)Context 包裝 handler,多個 Context 在 pipeline 中形成了雙向鏈表羽峰,入站方向叫 inbound趟咆,由 head 節(jié)點開始,出站方法叫 outbound 梅屉,由 tail 節(jié)點開始值纱。

7.4 ChannelPipeline調(diào)度handler的源碼剖析

(1)Context 包裝 handler薄湿,多個 Context 在 pipeline 中形成了雙向鏈表其馏,入站方向叫 inbound,由 head 節(jié)點開始少态,出站方法叫 outbound 玫霎,由 tail 節(jié)點開始凿滤;
(2)而節(jié)點中間的傳遞通過 AbstractChannelHandlerContext 類內(nèi)部的 fire 系列方法,找到當(dāng)前節(jié)點的下一個節(jié)點不斷的循環(huán)傳播庶近。是一個過濾器形式完成對handler 的調(diào)度翁脆。

7.5 Netty心跳(heartbeat)機制源碼剖析

Netty 提供了 IdleStateHandler ,ReadTimeoutHandler鼻种,WriteTimeoutHandler 三個Handler 檢測連接的有效性反番,具體內(nèi)容如下:

名稱 作用
IdleStateHandler 當(dāng)連接的空閑時間(讀或?qū)懀┨L時,將會觸發(fā)一個IdleStateEvent事件叉钥。然后罢缸,你可以通過你的ChannelInboundHandler中重寫userEventTrigged方法來處理該事件。
ReadTimeoutHandler 如果在指定的事件沒有發(fā)生讀事件投队,就會拋出這個異常枫疆,并自動關(guān)閉這個連接。你可以在exceptionCaught方法中處理這個異常敷鸦。
WriteTimeoutHandler 當(dāng)一個寫操作不能在一定的時間內(nèi)完成時息楔,拋出此異常寝贡,并關(guān)閉連接。你同樣可以在exceptionCaught方法中處理這個異常值依。

7.6 Netty 核心組件 EventLoop源碼剖析

EventLoop是一個單例線程池圃泡,里面含有一個死循環(huán)的線程不斷的處理著3個邏輯:監(jiān)聽端口、處理端口事件愿险、處理隊列事件颇蜡,每個EventLoop都可以綁定多個Channel,而每個Channel都只能有一個EventLoop來處理辆亏。

7.7 handler中加入線程池和Context 中添加線程池的源碼剖析

在 Netty 中有很多耗時的风秤,不可預(yù)料的操作,比如連接數(shù)據(jù)庫扮叨,網(wǎng)絡(luò)請求等唁情,這些會嚴(yán)重影響 Netty 對 Socket 的處理速度;而解決方法就是將耗時任務(wù)添加到異步線程池中甫匹。但就添加線程池這步操作來講,可以有兩種方式:
(1)第一種方式:在handler 中加入線程池
(2)第二種方式:在Context 中添加線程池

文章內(nèi)容輸出來源:拉勾教育Java高薪訓(xùn)練營
若有錯誤之處惦费,歡迎留言指正~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兵迅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子薪贫,更是在濱河造成了極大的恐慌恍箭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞧省,死亡現(xiàn)場離奇詭異扯夭,居然都是意外死亡,警方通過查閱死者的電腦和手機鞍匾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門交洗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橡淑,你說我怎么就攤上這事构拳。” “怎么了梁棠?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵置森,是天一觀的道長。 經(jīng)常有香客問我符糊,道長凫海,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任男娄,我火速辦了婚禮行贪,結(jié)果婚禮上漾稀,老公的妹妹穿的比我還像新娘。我一直安慰自己瓮顽,他們只是感情好县好,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著暖混,像睡著了一般缕贡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拣播,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天晾咪,我揣著相機與錄音,去河邊找鬼贮配。 笑死谍倦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泪勒。 我是一名探鬼主播昼蛀,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼圆存!你這毒婦竟也來了叼旋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤沦辙,失蹤者是張志新(化名)和其女友劉穎夫植,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體油讯,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡详民,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了陌兑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沈跨。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诀紊,靈堂內(nèi)的尸體忽然破棺而出谒出,到底是詐尸還是另有隱情,我是刑警寧澤邻奠,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布笤喳,位于F島的核電站,受9級特大地震影響碌宴,放射性物質(zhì)發(fā)生泄漏杀狡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一贰镣、第九天 我趴在偏房一處隱蔽的房頂上張望呜象。 院中可真熱鬧膳凝,春花似錦、人聲如沸恭陡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽休玩。三九已至著淆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拴疤,已是汗流浹背永部。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呐矾,地道東北人苔埋。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蜒犯,于是被迫代替她去往敵國和親组橄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349