Netty
最流行的 NIO 框架,由 JBOSS 提供的,整合了FTP,SMTP,HTTP協(xié)議
- API 簡(jiǎn)單
- 成熟穩(wěn)定
- 社區(qū)活躍·
- 經(jīng)過(guò)大規(guī)模驗(yàn)證(互聯(lián)網(wǎng)、大數(shù)據(jù)、網(wǎng)絡(luò)游戲、電信通信)
Elasticsearch、Hadoop 子項(xiàng)目 avro項(xiàng)目、阿里開(kāi)源框架 Dubbo、使用 Netty
BIO

優(yōu)點(diǎn):模型簡(jiǎn)單,編碼簡(jiǎn)單
缺點(diǎn):性能瓶頸,請(qǐng)求數(shù)和線程數(shù) N:N 關(guān)系
高并發(fā)情況下 ,CPU 切換線程上下文損耗大
案例:Tomcat 7之前,都是 BIO,7 之后是 NIO
改進(jìn):偽 NIO,使用線程池去處理邏輯
IO 模式
同步阻塞:丟衣服->等洗衣機(jī)洗完->再去晾衣服
同步非阻塞:丟衣服->去做其他事情,定時(shí)去看衣服是否洗完->洗完后自己去晾衣服
異步非阻塞:丟衣服-> 去做其他事情不管了,衣服洗好會(huì)自動(dòng)晾好,并且通知你晾好了
五種 I/O 模型
五種 I/O 模型:
阻塞 IO、非阻塞 IO、多路復(fù)用 IO、信號(hào)驅(qū)動(dòng) IO、異步 IO,前 4 種是同步 IO,在內(nèi)核數(shù)據(jù) copy 到用戶空間時(shí)是阻塞的
阻塞 IO

非阻塞 IO

IO 多路復(fù)用

核心:可以同時(shí)處理多個(gè) connection,調(diào)用系統(tǒng) select 和 recvfrom函數(shù)
每一個(gè)socket 設(shè)置為 non-blocking 阻塞是被 select 這個(gè)函數(shù) block 而不是 socket阻塞
缺點(diǎn):連接數(shù)不高的情況下,性能不一定比 多線程+ 阻塞 IO 好(多調(diào)用一個(gè)select 函數(shù))
信號(hào)驅(qū)動(dòng)

異步 IO
采用 Future-Listener機(jī)制

IO 操作分為 2 步:
- 發(fā)起 IO 請(qǐng)求,等待數(shù)據(jù)準(zhǔn)備
- 實(shí)際的 IO 操作,將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中
阻塞 IO、非阻塞 IO 區(qū)別在于發(fā)起 IO 請(qǐng)求是否被阻塞
同步 IO、異步 IO 在于實(shí)際的 IO 讀寫(xiě)是否阻塞請(qǐng)求進(jìn)程
阻塞非阻塞是線程的狀態(tài)
同步和異步是消息的通知機(jī)制
同步需要主動(dòng)讀寫(xiě)數(shù)據(jù),異步不需要主動(dòng)讀寫(xiě)數(shù)據(jù)
同步 IO 和異步 IO 是針對(duì)用戶應(yīng)用程序和內(nèi)核的交互
IO 多路復(fù)用
I/O 是指網(wǎng)絡(luò) I /O ,多路指多個(gè) TCP 連接,復(fù)用指一個(gè)或幾個(gè)線程。
簡(jiǎn)單來(lái)說(shuō):就是使用一個(gè)或者幾個(gè)線程處理多個(gè) TCP 連接,最大優(yōu)勢(shì)是減少系統(tǒng)開(kāi)銷(xiāo),不必創(chuàng)建過(guò)多的線程進(jìn)程,也不必維護(hù)這些線程進(jìn)程
select
文件描述符 writefds、readdfs、exceptfds
30w個(gè)連接會(huì)阻塞住,等數(shù)據(jù)可讀、可寫(xiě)、出異常、或者超時(shí)返回
select 函數(shù)正常返回后,通過(guò)遍歷 fdset整個(gè)數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件,來(lái)找到就緒的描述符fd,然后進(jìn)行對(duì)應(yīng)的 IO操作,幾乎在所有的平臺(tái)上支持,跨平臺(tái)支持性好
缺點(diǎn):
- select采用輪詢(xún)的方式掃描文件描述符,全部掃描,隨著文件描述符 FD 數(shù)量增多而性能下降。
- 每次調(diào)用 slect (),需要把 fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),并進(jìn)行遍歷(消息傳遞都是內(nèi)核到用戶空間)
- 最大缺陷就是單個(gè)進(jìn)程打開(kāi)的FD 有限制,默認(rèn)是 1024
poll
基本流程和 select差不多,處理多個(gè)描述符也是輪詢(xún),根據(jù)描述符的狀態(tài)進(jìn)行處理,一樣需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),并進(jìn)行遍歷。
區(qū)別是poll 沒(méi)有最大文件描述符限制(使用鏈表方式存儲(chǔ)fd)
epoll
沒(méi)有描述符限制,用戶態(tài)拷貝到內(nèi)核態(tài)只需要一次使用事件通知,通過(guò)epoll_ctl注冊(cè)fd,一旦該fd 就緒,內(nèi)核就采用callback機(jī)制激活對(duì)應(yīng)的fd
優(yōu)點(diǎn):
- 沒(méi)有fd限制,所支持的 FD 上限是操作系統(tǒng)的最大文件句柄數(shù)(65535),1G 內(nèi)存大概支持 10W 句柄,支持百萬(wàn)連接的話,16G 內(nèi)存就可以搞定
- 效率高,使用回調(diào)通知而不是輪詢(xún)方式,不會(huì)隨著 FD 數(shù)目增加效率下降
- 通過(guò) callback 機(jī)制通知,內(nèi)核和用戶空間 mmap 同一塊內(nèi)存實(shí)現(xiàn)
缺點(diǎn):
編程模型比 select / poll 復(fù)雜
linux內(nèi)核核心函數(shù) - epoll_create() 系統(tǒng)啟動(dòng)時(shí),會(huì)向linux內(nèi)核申請(qǐng)一個(gè)文件系統(tǒng),b+樹(shù),返回epoll 對(duì)象,也是一個(gè)fd
- epoll_ctl() 操作epoll對(duì)象,在這個(gè)對(duì)象里面修改添加刪除對(duì)應(yīng)的鏈接fd,綁定一個(gè)callback函數(shù)
- epoll_wait() 判斷并完成對(duì)應(yīng)的 IO 操作
例子:100W 個(gè)連接,1W 個(gè)活躍,在 select ,poll,epoll中怎么樣表現(xiàn)
select :不修改宏定義,需要 1000 個(gè)進(jìn)程才能支持 100W 連接
poll:100W連接,遍歷都響應(yīng)不過(guò)來(lái),還有空間的拷貝消耗大量的資源
epoll: 不用遍歷fd,不用內(nèi)核空間和用戶空間數(shù)據(jù)的拷貝
如果 100W 個(gè)連接中,95W 活躍,則 poll 和 epoll差不多
Java的i/o
- jdk1.4之前是采用同步阻塞模型(BIO)
大型服務(wù)一般采用 C/C++,因?yàn)榭梢灾苯硬僮飨到y(tǒng)提供的異步 IO(AIO) - jdk1.4之后推出NIO,支持非阻塞 IO,jdk1.7 升級(jí)推出 NIO2.0,提供了AIO 功能,支持文件和網(wǎng)絡(luò)套接字的異步 IO
Netty 線程模型和 Reactor 模式
Reactor模式(反應(yīng)器設(shè)計(jì)模式),是一種基于事件驅(qū)動(dòng)的設(shè)計(jì)模式,在事件驅(qū)動(dòng)的應(yīng)用中,將一個(gè)或者多個(gè)客戶的請(qǐng)求進(jìn)行分離和調(diào)度。在事件驅(qū)動(dòng)的應(yīng)用中,同步地,有序地處理接受多個(gè)服務(wù)請(qǐng)求。屬于同步非阻塞 IO
優(yōu)點(diǎn):
- 響應(yīng)快,不會(huì)因?yàn)閱蝹€(gè)同步而阻塞,雖然 reactor本身是同步的
- 編程相對(duì)簡(jiǎn)單,最大程度避免復(fù)雜的多線程以及同步問(wèn)題,避免了多線程、進(jìn)程切換開(kāi)銷(xiāo)
- 可擴(kuò)展性,可以方便的通過(guò) reactor實(shí)例個(gè)數(shù)充分利用 CPU 資源
缺點(diǎn): - 相對(duì)復(fù)雜,不易于調(diào)試
- reactor模式需要系統(tǒng)底層的支持。比如java中的selector支持,操作系統(tǒng)select系統(tǒng)調(diào)用支持
Reactor 單線程模型
- 作為 NIO 服務(wù)器,接受客戶端 TCP 連接,作為 NIO 客戶端,向服務(wù)端發(fā)起 TCP 連接
- 服務(wù)端讀請(qǐng)求數(shù)據(jù)并響應(yīng),客戶端寫(xiě)請(qǐng)求并讀取響應(yīng)
場(chǎng)景:
對(duì)應(yīng)小業(yè)務(wù)則適合,編碼簡(jiǎn)單,對(duì)于高負(fù)載,高并發(fā)不合適。一個(gè) NIO 線程處理太多請(qǐng)求,負(fù)載很高,并且響應(yīng)變慢,導(dǎo)致大量請(qǐng)求超時(shí),萬(wàn)一線程掛了,則不可用
Reactor 多線程模型
一個(gè) Acceptor線程,一組 NIO 線程,一般是使用自帶線程池,包含一個(gè)任務(wù)隊(duì)列和多個(gè)可用線程
場(chǎng)景:
可滿足大多數(shù)場(chǎng)景,當(dāng)Acceptor需要做負(fù)責(zé)操作的時(shí)候,比如認(rèn)證等耗時(shí)操作 ,在高并發(fā)情況下也會(huì)有性能問(wèn)題
Reactor 主從線程模型
Acceptor不在是一個(gè)線程,而是一組 NIO 線程,IO 線程也是一組 NIO 線程,這樣就是 2 個(gè)線程池去處理接入和處理 IO
場(chǎng)景:
滿足目前大部分場(chǎng)景,也是 Netty推薦使用的線程模型
BossGroup 處理連接的
WorkGroup 處理業(yè)務(wù)的
Netty 使用 NIO 而不是 AIO
在 linux系統(tǒng)上,AIO 的底層實(shí)現(xiàn)仍然使用 epoll,與 NIO 相同,因此在性能上沒(méi)有明顯的優(yōu)勢(shì)
Netty 整體架構(gòu)是 reactor 模型,采用 epoll機(jī)制,IO 多路復(fù)用,同步非阻塞模型
Netty是基于 Java NIO 類(lèi)庫(kù)實(shí)現(xiàn)的異步通訊框架
特點(diǎn): 異步非阻塞,基于事件驅(qū)動(dòng),性能高,高可靠性,高可定制性。
Echo服務(wù)
回顯服務(wù),用于調(diào)試和檢測(cè)的服務(wù)
源碼剖析
EventLoop和EventLoopGroup
高性能 RPC框架 3 個(gè)要素:IO 模型、數(shù)據(jù)協(xié)議(http,brotobuf/thrift)、線程模型
EventLoop 好比一個(gè)線程,一個(gè) EventLoop可以服務(wù)多個(gè)Channel,一個(gè)Channel只有一個(gè)EventLoop,可以創(chuàng)建多個(gè)EventLoop來(lái)優(yōu)化資源的利用,也就是EventLoopGroup
一個(gè)Cahnnel 一個(gè)連接,EventLoopGroup 負(fù)責(zé) EventLoop
NIO(單線程處理多個(gè)Channels) BIO(一個(gè)線程處理一個(gè)Channels)
事件: accept,connect,read,write
EventLoopGroup 默認(rèn)創(chuàng)建線程數(shù)是 CPU 核數(shù) * 2
Bootstrap
- group:設(shè)置線程中模型,Reactor線程模型對(duì)比EventLoopGroup
- 單線程
EventLoopGroup g = new NioEventLoopGroup(1);
ServerBootstrap strap = new ServerBootstrap();
strap.group(g)
- 多線程
- 主從線程
channel
NioServerSocketChannel
OioServerSocketChannel
EpollServerSocketChannel
KQueueServerSocketChannel
childHandler
用于對(duì)每個(gè)通道里面的數(shù)據(jù)處理
childOption
作用于被 accept之后的連接
option
作用于每個(gè)新建立的 channel,設(shè)置 TCP 連接中的一些參數(shù)
-
ChannelOption.SO_BACKLOG
存放已完成三次握手的請(qǐng)求的等待隊(duì)列的最大長(zhǎng)度
Linux 服務(wù)器 TCP 連接底層知識(shí):
syn queue: 半連接隊(duì)列,洪水攻擊(偽造 IP 海量發(fā)送第一個(gè)握手包),tcp_max_syn_backlog (修改半連接 vi /etc/sysctl.conf)
accept queue:全連接隊(duì)列 net.core.somaxconn 當(dāng)前機(jī)器最大連接數(shù)
系統(tǒng)默認(rèn)的somaxconn參數(shù)要足夠大,如果 backlog 比 somaxconn大,則會(huì)優(yōu)先用后者
- ChannelOption.TCP_NODELAY
默認(rèn)是 false,要求高實(shí)時(shí)性,有數(shù)據(jù)時(shí)馬上發(fā)送,就將該值改為 true 關(guān)閉 Nagle 算法 (Nagle算法會(huì)積累一定大小后再發(fā)送,為了減少發(fā)送次數(shù))Nagle算法只允許一個(gè)未被 ACK 的包存在于網(wǎng)絡(luò)
(tcp_synack_retries = 0 加快回收半連接,如果收不到第三個(gè)握手包 ACK,不進(jìn)行重試,默認(rèn)值是 5,每次等待 30S,半連接會(huì) hold住大約 180s,tcp_syn_retries 默認(rèn)值是 5,客戶端沒(méi)收到 SYN+ACK 包,客戶端也會(huì)重試 5 次發(fā)送 SYN 包)
childOption
作用于被 accept之后的鏈接
childHandler
用于對(duì)每個(gè)通道里面的數(shù)據(jù)處理
Channel
- Channel
客戶端和服務(wù)端建立的一個(gè)連接通道 - ChannelHandler
負(fù)責(zé)Channel的邏輯處理 - ChannelPipeline
負(fù)責(zé)管理 ChannelHandler的有序容器
一個(gè)Channel包含一個(gè)ChannelPipeline,所有 ChannelHandler都會(huì)順序加入到ChannelPipeline中。
Channel當(dāng)狀態(tài)出現(xiàn)變化,對(duì)觸發(fā)對(duì)應(yīng)的事件

狀態(tài):
- channelRegistered
channel注冊(cè)到一個(gè)EventLoop,和Selector綁定 - channelUnRegistered
channel已創(chuàng)建,但是未注冊(cè)到一個(gè)EventLoop里面,也就是沒(méi)有和Selector綁定 - channelActive
變?yōu)榛钴S狀態(tài),連接到了遠(yuǎn)程主機(jī),可以接受和發(fā)送數(shù)據(jù) - channelInActive
channel處于非活躍狀態(tài),沒(méi)有連接到遠(yuǎn)程主機(jī)
ChannelHandler和ChannelPipeline
ChannelHandler生命周期:
handlerAdded:當(dāng)ChannelHandler添加到ChannelPipeline調(diào)用
handlerRemoved:當(dāng)ChannelHandler從ChannelPipeline移除時(shí)調(diào)用
exceptionCaught:執(zhí)行拋出異常時(shí)調(diào)用
ChannelHandler有 2 個(gè)子接口:

ChannelInboundHandler(入站): 處理輸入數(shù)據(jù)和Channel狀態(tài)類(lèi)型改變,適配器 ChannelInboundHandlerAdapter(適配器設(shè)計(jì)模式),常用 SimpleChannelInboundHandler
ChannelOutboundHandler(出站):處理輸出數(shù)據(jù),適配器 Channel
ChannelPipeline:
好比廠里的流水線一樣,可以在上面添加多個(gè)ChannelHandler,也可以看成是一串 ChannelHandler 實(shí)例,攔截穿過(guò)Channel的輸入輸出 event,ChannelPileline實(shí)現(xiàn)了攔截器的一種高級(jí)形式,使得用戶可以對(duì)事件的處理以及ChannelHandler之間交互獲得完全的控制權(quán)
ChannelHandlerContext

channelHandlerContext 是連接 ChannelHandler 和 ChannelPipeline 的橋梁
ChannelHandlerContext 部分方法是和 Channel以及ChannelPipleline重合,好比調(diào)用 write方法
Channel,ChannelPipeline,ChannelHandlerContext都可以調(diào)用寫(xiě)方法,前 2 者會(huì)在整個(gè)管道流里傳播,而 ChannelHandlerContext只會(huì)在后續(xù)的 Handler里傳播AbstractChannelHandlerContext
雙向鏈表結(jié)構(gòu),next/prev 后繼、前驅(qū)節(jié)點(diǎn)DefaultChannelHandlerContext 是實(shí)現(xiàn)類(lèi),但是大部分都是父類(lèi)完成,整個(gè)只是簡(jiǎn)單的實(shí)現(xiàn)一些方法,主要就是判斷 Handler的類(lèi)型
fire調(diào)用下一個(gè) handler,不fire就不調(diào)用
Handler執(zhí)行順序
InboundHandler順序執(zhí)行,OutboundHandler逆序執(zhí)行
channel.pipeline().addLast(new OutboundHandler1());
channel.pipeline().addLast(new OutboundHandler2());
channel.pipeline().addLast(new InboundHandler1());
channel.pipeline().addLast(new InboundHandler2());
InboundHandler1 InboundHandler2 OutboundHandler2 OutboundHandler1
InboundHandler1之間通過(guò) fireChannelRead()方法調(diào)用
InboundHandler通過(guò)ctx.write(msg),傳遞到OutboundHandler
ctx.write(msg)傳遞消息,Inbound需要放在結(jié)尾,在 outbound之后,不然outboundHandler不會(huì)執(zhí)行,使用 channel.write(msg),或者 pipline.write(msg),就不用考慮(傳播機(jī)制)
客戶端: 發(fā)起請(qǐng)求再接受請(qǐng)求,先 outbound再inbound
服務(wù)端:先接受請(qǐng)求再發(fā)送請(qǐng)求,先inbound再outbound
ChannelFuture
netty中所有 I/0 操作都是異步的,意味著任何 I/0 調(diào)用都會(huì)立即返回,而ChannelFuture會(huì)提供有關(guān)的信息 I/0 操作的結(jié)果或狀態(tài)
未完成:
當(dāng) I/0 操作開(kāi)始時(shí),將創(chuàng)建一個(gè)新的對(duì)象,新的最初是未完成的,它既沒(méi)有成功,也沒(méi)有被取消,因?yàn)?I/0 操作尚未完成
。
已完成:當(dāng) I/0 操作完成,不管是成功、失敗還是取消,F(xiàn)uture都是標(biāo)記為已完成的,失敗的時(shí)候也有具體的信息,例如原因失敗,但請(qǐng)注意,即使失敗和取消屬于完成狀態(tài)。

注意:不要在 IO 線程內(nèi)調(diào)用Future對(duì)象的sync和await方法,不能在 channelhandler中調(diào)用 sync 和 await
ChannelPromise
繼承 ChannelFuture,進(jìn)一步擴(kuò)展用于設(shè)置 IO 操作的結(jié)果
編解碼
java序列化/反序列化,url編解碼,base64編解碼
java自帶序列化的缺點(diǎn):
- 無(wú)法跨語(yǔ)言
- 序列化后的碼流太大,數(shù)據(jù)包太大
- 序列化和反序列化性能比較差
業(yè)界其他編解碼框架:PB,Thrift,Marshalling,Kyro
Netty里面的編解碼:
- 解碼器:主要負(fù)責(zé)處理入站 InboundHandler
- 編碼器: 主要負(fù)責(zé)處理出站 OutBoundHandler
Netty默認(rèn)編解碼器,也支持自定義編解碼器
Encoder(編碼器),Decoder(解碼器),Codec(編解碼器)
Netty解碼器 Decoder
Decoder對(duì)應(yīng) ChannelInboundHandler,主要就是字節(jié)數(shù)組轉(zhuǎn)換成消息對(duì)象
方法:
- decode :常用
- decodeLast: 用于最后的幾個(gè)字節(jié)處理,也就是 cahnnel 關(guān)閉的時(shí)候,產(chǎn)生的最后一個(gè)消息
解碼器: - ByteToMessageDecoder
用于將字節(jié)轉(zhuǎn)為消息,需要檢查緩沖區(qū)是否有足夠的字節(jié) - ReplayingDecoder
繼承ByteToMessageDecoder,不需要檢查緩沖區(qū)是否有足夠多的數(shù)據(jù),速度略慢于 ByteToMessageDecoder - MessageToMessageDecoder
用于將一種消息解碼到另外一種消息(例如 POJO 到 POJO)
常用的解碼器:(主要解決 TCP 底層的粘包和拆包問(wèn)題) - DelimiterBasedFrameDecoder:執(zhí)行消息分隔符的解碼器
- LineBasedFrameDecoder:以換行符為結(jié)束標(biāo)志的解碼器
- FixedLengthFrameDecoder:固定長(zhǎng)度的解碼器
- LengthFieldBasedFrameDecoder: message = header + body,基于長(zhǎng)度解碼的通用解碼器
- StringDecoder:文本解碼器,將接收到的消息轉(zhuǎn)為字符串,一般會(huì)與上面的幾種進(jìn)行組合,然后再后面加業(yè)務(wù)的 handler
Netty 編碼器 Encoder
Encoder 對(duì)應(yīng)就是 ChannelOutboundHandler ,消息對(duì)象轉(zhuǎn)換成字節(jié)數(shù)組
編碼器:
- MessageToByteEncoder
消息轉(zhuǎn)為字節(jié)數(shù)組,調(diào)用 write方法,會(huì)先判斷當(dāng)前編碼器是否支持需要發(fā)送的消息類(lèi)型,如果不支持,則透?jìng)?/li> - MessageToMessageEncoder 從一種消息編碼為另外一種消息
Netty 組合編解碼器 Codec
優(yōu)點(diǎn):成對(duì)出現(xiàn),編解碼都是在一個(gè)類(lèi)里完成
缺點(diǎn):耦合,擴(kuò)展性不佳
- ByteToMessageCodec
- MessageToMessageCode
TCP 粘包,拆包

TCP 拆包:一個(gè)完整的包可能被 TCP 拆分成多個(gè)包進(jìn)行發(fā)送
TCP 粘包:把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送,client發(fā)送的若干數(shù)據(jù)包, server接收時(shí)粘在一個(gè)包
發(fā)送方和接收方都可能出現(xiàn)這個(gè)原因
發(fā)送方的原因:TCP 默認(rèn)會(huì)使用 Nagle算法
接收方的原因:TCP 接收到數(shù)據(jù)放置緩存中,應(yīng)用程序從緩存中讀取比較慢
UDP 無(wú)粘包、拆包問(wèn)題,有邊界協(xié)議
TCP 半包讀寫(xiě)解決方案
發(fā)送方:關(guān)閉 Nagle 算法
接收方:TCP 是無(wú)界的數(shù)據(jù)流,并沒(méi)有處理粘包現(xiàn)象的機(jī)制,且協(xié)議本身無(wú)法避免粘包,半包讀寫(xiě)的發(fā)生需要在應(yīng)用層進(jìn)行處理
應(yīng)用層解決半包讀寫(xiě)方法:
- 設(shè)置定長(zhǎng)消息 (10 個(gè)字符)
abcdefgh11abcdefgh11abcdefgh11 - 設(shè)置消息邊界 (
dsfsdfdsf$dsfdsfsdf
- 使用帶消息頭的協(xié)議,消息頭存儲(chǔ)消息開(kāi)始標(biāo)識(shí)及消息的長(zhǎng)度信息
header + body
Netty 自帶解決 TCP 半包讀寫(xiě)方案
- DelimiterBasedFrameDecoder:指定消息分隔符的解碼器
- LineBasedFrameDecoder:以換行符為結(jié)束標(biāo)志的解碼器
- FixedLengthFrameDecoder:固定長(zhǎng)度解碼器
- LengthFieldBasedFrameDecoder : message = header + body ,基于長(zhǎng)度解碼的通用解碼器
實(shí)戰(zhàn)半包讀寫(xiě)
LineBasedFrameDecoder:以換行符為結(jié)束標(biāo)志的解碼器
StringDecoder 解碼器將對(duì)象轉(zhuǎn)成字符串
自定義分隔符解決 TCP 讀寫(xiě)問(wèn)題
DelimiterBasedFrameDecoder
maxLength: 表示一行最大的長(zhǎng)度,超過(guò)長(zhǎng)度依然沒(méi)檢測(cè)自定義分隔符,拋出TooLongFrameException
failFast: 如果為true,則超過(guò) maxLength后立即拋出TooLongFrameException,不進(jìn)行繼續(xù)解碼,如果為 false,則等到完整消息被解碼后,再拋出TooLongFrameException
stripDelimiter:解碼后的消息是否去除分隔符
delimiters:分隔符,ByteBuf類(lèi)型
自定義長(zhǎng)度半包讀寫(xiě)器 LengthFieldBasedFrameDecoder
maxFrameLength 數(shù)據(jù)包最大長(zhǎng)度
lengthFieldOffset 長(zhǎng)度字段的偏移量,長(zhǎng)度字段開(kāi)始的地方(跳過(guò)指定長(zhǎng)度個(gè)字節(jié)之后的才是消息體字段)
lengthFieldLength 長(zhǎng)度字段占的字節(jié)數(shù),幀數(shù)據(jù)長(zhǎng)度的字段本身的長(zhǎng)度
lengthAdjustment
一般 Header + Body ,添加到長(zhǎng)度字段的補(bǔ)償值,如果為負(fù)數(shù),開(kāi)發(fā)人員認(rèn)為這個(gè)Header的長(zhǎng)度字段是整個(gè)消息包的長(zhǎng)度,,則Netty應(yīng)該減去對(duì)應(yīng)的數(shù)字
initialBytesToStrip 從解碼幀中第一次去除的字節(jié)數(shù),獲取完一個(gè)完整的數(shù)據(jù)包之后,忽略前面的指定位數(shù)的長(zhǎng)度字節(jié),應(yīng)用解碼器拿到的就是不帶長(zhǎng)度域的數(shù)據(jù)包
ByteBuf

字節(jié)容器,
- JDK 中原生 ByteBuffer
讀和寫(xiě)公用一個(gè)索引,每次換操作都需要Flip()
擴(kuò)容麻煩,而且擴(kuò)容后容易造成浪費(fèi) - Netty ByteBuf
讀寫(xiě)使用不同的索引,所以操作便捷
自動(dòng)擴(kuò)容,便捷
ByteBuf 創(chuàng)建方法與常見(jiàn)的模式
ByteBuf:傳遞字節(jié)數(shù)據(jù)的容器
ByteBuf的創(chuàng)建方法:
- ByteBufAllocator
Netty 4.x之后默認(rèn)使用池化(PooledByteBufAllocator)提高性能,最大程度減少內(nèi)存碎片
非池化:UnPooledByteBufAllocator 每次返回一個(gè)新的實(shí)例 - Unpooled:提供靜態(tài)方法創(chuàng)建未池化的ByteBuf,可以創(chuàng)建堆內(nèi)存和直接內(nèi)存緩沖區(qū)
ByteBuf使用模式:
- 堆緩存區(qū)
優(yōu)點(diǎn):heap buffer 存儲(chǔ)在 jvm的堆空間中,快速的分配和釋放
缺點(diǎn):每次使用前會(huì)拷貝到直接緩存區(qū) (堆外內(nèi)存) - 直接緩存區(qū)
Direct buffer
優(yōu)點(diǎn):不用占用 JVM 的堆內(nèi)存,存儲(chǔ)在堆外內(nèi)存
缺點(diǎn):內(nèi)存的分配和釋放,比在堆緩存區(qū)更復(fù)雜 - 復(fù)合緩沖區(qū)
創(chuàng)建多個(gè)不同的 ByteBuf,然后放在一起,但是只是一個(gè)視圖
選擇:大量 IO 數(shù)據(jù)讀寫(xiě),用直接緩存區(qū),業(yè)務(wù)消息編解碼用堆緩存區(qū)
Netty 設(shè)計(jì)模式
Builder 構(gòu)造器模式:ServerBootstrap
責(zé)任鏈設(shè)計(jì)模式:pipeline的事件傳播
工廠模式:創(chuàng)建 channel
適配器模式:HandlerAdapter
Netty 單機(jī)百萬(wàn)實(shí)戰(zhàn)
- 網(wǎng)絡(luò) IO模型
- Linux文件描述符
單進(jìn)程文件描述符(句柄數(shù)),每個(gè)進(jìn)程都有最大的文件描述符限制
全局文件句柄數(shù),也有默認(rèn)值,不同系統(tǒng)版本會(huì)不一樣 -
如何確定唯一 TCP 連接
TCP 四元組:源 IP,源端口,目標(biāo) IP,目標(biāo)端口
服務(wù)端端口范圍(1024~65535)
65545
優(yōu)化: - sudo vim /etc/security/limits.conf 修改局部 fd數(shù)目,修改后要重啟,ulimit -n 查看當(dāng)前這個(gè)用戶每個(gè)進(jìn)程最大 FD 數(shù)
root soft nofile 1000000
root hard nofile 1000000
* soft nofile 1000000
* hard nofile 1000000
- sudo vim /etc/sysctl.conf 修改全局 fd 數(shù)目
fs.file-max=1000000
sysctl -p 重啟生效參數(shù)
cat /proc/sys/fs/file-max 查看全局fd數(shù)目
- 重啟生效 reboot
-Xms5g -Xmx5g -XX:NewSize=3g -XX:MaxNewSize=3g
數(shù)據(jù)鏈路
瀏覽器同域名下資源加載有并發(fā)數(shù)限制,建議不同資源用不同域名
輸入域名-》瀏覽器內(nèi)核調(diào)度-》本地 DNS 解析-》遠(yuǎn)程 DNS解析
-》IP-》路由多層跳轉(zhuǎn)-》目的服務(wù)器-》服務(wù)器內(nèi)核-》應(yīng)用程序
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!

