什么是nio?
nio 本質就是多路復用碍脏,內核告訴你那些句柄可讀可寫
jdk的selector就是對操作系統(tǒng)的io多路復用的一個封裝窃蹋,在linux中就是對epoll的封裝准潭;epoll的本質就是讓內核直接處理句柄者铜,不需要再復制到用戶空間腔丧,這個要整理不能簡單介紹
================>傳送門(后續(xù)整理)
非nio的io復用的使用:
客戶端:
打開socketchannel 設置 socketchannel為非阻塞 異步鏈接server ,向reactor注冊多路復用器作烟,reactor線程創(chuàng)建selector啟動線程愉粤,輪詢就緒的key 異步讀請求到bytebuffer (異步寫請求到socketchannel)
服務端:
打開serversocketchannel 綁定端口,Reactor創(chuàng)建selector 將serversocketchannel注冊到selector selector輪詢key拿撩。判斷是否是新接入衣厘,新接入設置為accept。不是新接入的可讀狀態(tài) 異步請求到byuteBuffer(異步寫butebuffer到socketchannel)
如果是netty不用這么麻煩(首先啟動bootstrapt 注入兩個線程組压恒,創(chuàng)建channel為nioServerSocketChannel影暴,然后配置nioserverSocketChannel的tcp參數,設置為backlog探赫,最后綁定iohandler 處理類(包含處理類型宙,編解碼,記錄日志等)伦吠,上面配置完成以后妆兑,綁定監(jiān)聽端口即可)
tcp拆包粘包:
產生原因:1.進行了mss大小的tcp分段? 2.超mtu進行ip分段 3.程序寫的數據超過緩沖期大小
解決策略:
1.定長消息
2.在包尾部進行分割符
換行:LineBasedFrameDecoder
分隔符:DelimiterBasedFrameDecoder?
定長(固定長度):FixLengthFrameFecoder?
3.在表頭表名消息的總長度
netty解決粘包的第一種方法:LineBasedFrameDecoder解決:
(通過addLast()添加編解碼器)
(服務端)
一:bootstrap.group(boss搁嗓,work).channel(NioserversocketChannel.class).OPTION( tcp參數).childHandler(子處理器)
二:子處理器(pipeline).addLast(LineBasedFrameDecoder編解碼).addLast(StringDecoder).addLast(真正的業(yè)務處理器handler)
三:真正的處理器子處理器handler需要繼承ChannelHandlerAdaptr父類實現 channelActive芯勘、channelRead,exceptionCaught方法
(客戶端)
與服務端類似:bootstrap.group().channel().option().handler(new ChannerlInitializer(){
實現initChannel(socketChannel)方法{
ch.pipeline.addLast(new LineBasedFrameDecoder()).addLast(StringDecoder()); addLast(客戶端處理類)}
})
:如此解決的問題:不會出現粘包的問題
為什么可以解決該問題:
原理很簡單LineBasedFrameDecoder的作用是依次遍歷bytebuf的可讀字節(jié)腺逛,讀到/N /R/N 換行符的時候結束荷愕,從可讀索引到結束位置的區(qū)間字節(jié)組成一行
而StringDecoder將接受的對象轉成字符串然后調用后來的handler,這兩個東西就是做按行切換的的文本解碼器
另外的處理方法:DelimiterBasedFrameDecoder 和 FixLengthFrameFecoder 這兩種的實現第一個是自動完成以分隔符做結束標志的消息的解碼
第二個是自動完成對定長消息的解碼
什么是netty的編解碼屉来?
當遠程調用的時候路翻,需要把本地的java對象編碼為字節(jié)數組或者bytebuffer對象,當遠程讀取到bytebuffer對象或者字節(jié)數組的時候茄靠,需要將其解碼位發(fā)送時的java對象
java序列化:僅僅是java編解碼技術的一種
為什么rpc框架很少使用jdk的serializable序列化:第一個缺點:因為rpc通常用到的是調用非java對象茂契,而jdk的serializable只是適用于java語言;第二個缺點:因為序列化的后碼流很大慨绳,占用網絡傳輸空間大掉冶;第三個缺點,序列化的性能比二進制編碼的性能差
編解碼技術二:google的protbuf
特點:結構化數據存儲格式(xml json) 脐雪;高校的編解碼技術厌小;語言無關;支持java.c python
使用二進制編碼有益于網絡傳輸
Thrift:
弱勢:必須先確定好對象的數據結構战秋。當數據結構發(fā)生變化了璧亚,需要重新定義idl(接口描述)文件
優(yōu)點:適用于大型數據的交換及存儲的工具。性能遠優(yōu)于json和xml
thrift主要由5部分組成:
1.語言系統(tǒng)及idl編譯器脂信,由用戶給定額idl文件生成相應的接口代碼
2.Tprotocol:Rpc的協議層癣蟋,選擇多種不同的序列化方式。如json和binary
3.TTransport:rpc的傳輸層狰闪,可以選擇不同的傳輸層實現疯搅。如socket。NIO埋泵。MemoryBuffer
4.Tprocessor:作為協議層和用戶提供的服務實現之間的紐帶幔欧,負責調用服務實現的接口
5.TServer:聚合上面的2.3.4
Thrift支持三種典型的編解碼:通用的二進制編解碼。壓縮的二進制編解碼丽声。優(yōu)化的可選字段壓縮編解碼
Netty使用Protobuf作為編解碼框架實現
優(yōu)點:跨語言礁蔗,編解碼后消息更小,編解碼性能高雁社,支持定義可選和必選字段
特點:支持數據結構化一次到處使用瘦麸,包括跨語言使用
netty實現protobuf:
前面配置一樣,bootstrap.group.channel.option.childHandler(pipeline.addlast(protobuVarine32FrameDecoder(半包處理)).addLast(new protobufEncoder()).addLast(new SubReqServerHandler()))
因為使用了Decoder所以消息直接自動解碼歧胁,收到的消息可以直接使用滋饲;由于使用了Encoder我們做響應的時候不需要手動進行編碼
客戶端:類似,pipeline.addLast(new protobufVarint32FrameDecoder).addLast(new ProtobufDecoder()).addLast(ProtobufVarint32LengthFieldPrepender).addLast(new ProtobufEncoder).addLast(handler)
支持http場景
netty天生的異步事件驅動框架屠缭,因此箍鼓,niotcp協議棧開發(fā)的http協議也是異步非阻塞的
WEBSOCKET協議開發(fā)
websocket的出現解決了http的弊端,1.http是個半雙工的協議呵曹,數據只能單一方向傳遞
2.http消息榮譽繁雜款咖,包含消息頭,消息體奄喂,換行符等铐殃,相比于其余二進制冗雜
照著寫一份代碼試一下懶得解釋了
netty的基本組件及源碼學習
ByteBuf:
首先我們數據傳輸用到緩沖區(qū),使用的最多主要是bytebuffer跨新,8中基本數據類型都用各自的bytebuffer富腊,這個是jdknio類庫提供使用的
缺點是
bytebuffer的長度是固定死的,出現了編碼的pojo大于bytebuffer的時候就會出現索引越界的異常
2.bytebuffer只有一個標志位置的指針position 讀寫的時候需要手動調用filp()和rewind()等要正確調用否則會出現異常
光明出現了:Netty提供了新的緩沖區(qū)bytebuf數組緩沖區(qū)
提供一下幾個基本功能:
1.支持7種劇本類型byte數組域帐,bytebuffer等的讀寫
2.緩沖區(qū)自身的copy和slice
3.設置網絡字節(jié)序
4.構造緩沖區(qū)實例
5.操作位置指針等方法
BYTEBUFFER的實現:如果用的是jdk的bytebuffer赘被,要正確使用filp,如果寫入數據后不調用flip的話肖揣。讀取的內容不正確
所以得出結論 : flip()做的是將position與初始位置交換民假,limit移動到position的位置
bytebuf的實現
采用了readIndex和writeIndex兩個指針
clear以后數據都兩個指針都回到0位置
Bytebuffer進行擴容龙优,他不能自動擴容羊异,put數據的時候會判斷剩余空間是否足夠,如果不夠的話會進行重新開辟新的空間彤断,將老數據移入野舶,并清理老的bytebuffer對象;而netty提供的新的bytebuf會自動擴容不需要人為進行手動擴容
clear:將讀寫指針移會到初始位置
Mark+Reset:
bytebuffer用的是這兩個瓦糟,主要的用處是mark將當前指針記錄筒愚,reset將指針恢復為備份在mark中的值
bytebuf因為有兩個指針赴蝇,所以對應的mark和reset有四個菩浙,分別是讀mark讀reset寫mark寫reset
已知:nio的socketchannel進行網絡讀寫。操作的對象是bytebuffer句伶。
但是:netty所用的是bytebuf他不能操作
所以:要在接口層進行兩者互換劲蜻,
詳解bytebuffer與bytebuf轉換:
1.nioBuffer()將bytebuf轉換成bytebuffer,兩者公用一個緩沖區(qū)內容考余,對bytebuffer的讀寫操作先嬉,不會修改原bytebuf的動態(tài)拓展操作
2.niobuffer(int index , int length)也是將bytebuf轉換成buffer楚堤,是在起始位置為index疫蔓,長度為length進行轉換
支持隨機讀寫(set / get)
但是問題在set和write的區(qū)別是set必須確認長度大小是否符合可寫入的大小含懊,不能支持動態(tài)擴容,而write是支持動態(tài)擴容的
Bytebuf分類
按照內存分配的角度衅胀,可以分為兩類:堆內存和直接內存
堆內存:
分配及回收速度快岔乔,可以被jvm自動回收,但是缺點是io讀寫的時候要額外做一次內存復制滚躯,將堆內存的緩沖區(qū)復制到內核的channel中
直接內存
非堆內存雏门,在堆外進行內存分配,他的分配和回收會比堆內存比較慢掸掏,但是io操作的時候可以直接從socketchannel中讀取
最佳實踐:
io通信線程讀寫緩沖區(qū)使用直接內存茁影。后端業(yè)務編解碼模塊使用堆內存
按照內存回收的角度區(qū)分bytebuf可以以分為兩類,基于對象池的bytebuf 普通的bytebuf
區(qū)別在于對象池的bytebuf可以復用bytebuf對象丧凤,可以在netty的高負載大并發(fā)的沖擊下使得gc更加的穩(wěn)定
Channel是個啥
是jdk的NIO的組成部分募闲,netty重新實現
主要的api還是進行網絡io
channel在netty中是網絡操作抽象類、他聚合了一組功能息裸,包含但不限于網絡的讀寫客戶端發(fā)起鏈接主動關閉鏈接蝇更,獲取通信雙方餓網絡地址等;同時也可以獲取eventloop呼盆,獲取緩沖分配器年扩,和pipeline等
netty用自己的channel主要是要能夠跟netty的整體架構融合, 例如io模型访圃,基于channelpipeline的定制模型厨幻,以及基于元數據描述配置化的tcp參數等
channel的一些常用api
1.channel read:從channel中讀取數據到第一個inbound緩沖區(qū),讀取完以后會調用channelReadComplete這樣決定是否需要繼續(xù)讀取數據
2.channelFuture write:將數據通過channelpipeline寫入到channel中腿时,該操作只是將消息發(fā)送到環(huán)形數組中况脆,只有當調用到flush才會真正的發(fā)送數據
3.channelfuture writeandflush 將write和flush組合
4.channel flush將之前寫入到發(fā)送環(huán)形數組的消息全部寫入channel 并發(fā)送給通信方
5.channelfure connect 指定服務器地址發(fā)起鏈接請求
6.channelfuture bind 綁定指定的本地socket地址
7.is open:判斷是否channel已經打開;isRegistered:判斷channel是否注冊到evenloop上
8.eventLoop():channel需要注冊到evenloop的多路復用器批糟;通過該方法格了,可以獲取到channel注冊的evenloop (evenloop本質上是吃力網絡讀寫的reactor線程)
netty網絡傳播的本質是,當有網絡io的操作時候會發(fā)生channelPipeline中對應的事件方法徽鼎;channel在io操作中產生的io事件盛末,驅動事件在channelpieline中傳播。由對應的channelhandler進行事件處理否淤。不關心的事件直接忽略悄但。功能等價于aop像切面一樣
兩個重要的channel介紹一下 nioServerSocketChannel 和 nioSocketChannel
nioServerSocketChannel
對于nioserversocketchannel來說,他的讀取操作就是接受客戶端的鏈接石抡,然后創(chuàng)建niosocketChannel對象
nioSocketChannel
1.鏈接操作 socket.bind + socketchannel.connect檐嚣;如果鏈接success將niosocketChannel中的selectionKey設置為OP_CONNECT
什么是ChannelPipeline 和 ChannelHandler
就是一種責任鏈模式
Netty將channel的數據管道抽象為channelpipeline ,消息在channelPipeline中傳遞啰扛。channelHandler為事件攔截器嚎京,被channelpipeline持有(pipeline是handler的容器)嗡贺,用戶可以通過新增和刪除channelhandler進行不同的業(yè)務處理,不需要對已有的handler進行修改鞍帝。
本章三個概念:channelpipeline channelhandler channelhandlerContext
pipeline的流程圖詳解
左邊一列:數據讀取的流程暑刃,底層的socketchannel 調用read()讀取bytebuf -----> 觸發(fā)io線程eventloop調用channelpipeline的fireChannelRead的方法闷供。將消息(bytebuf)傳輸到channelPipeline中 --------->依次被channelhandler(1.2.3.4)一直到tailHandler(任何一個channelhandler都可以攔截和處理當前的消息結束消息傳遞
右邊一列:調用channelContext的write方法粟关,從tailHandler開始,途徑channelHandler(N...1)將消息發(fā)送到緩沖區(qū)中等待刷新向臀,也可以中斷流程如編碼失敗的時候宵膨,需要中斷消息構造future返回
Netty的事件:inbound事件架谎。outbound事件(參看上面的流程圖)這兩個事件只是netty根據事件在pipeline中流向抽象出來的術語
inbound事件:(流程圖左邊)(都是執(zhí)行的channelHandlerContext的 方法)(從io線程流向用戶業(yè)務的handler)
1.channel注冊 》?channelHandlerContext.fireChannelRegistered()
2.TCP鏈路建立成功,Channel激活事件
3.讀事件
4.讀完之后的通知事件
5.異常通知事件
6.Channel的可寫狀態(tài)變化通知事件
7.TCP關閉
outbound事件(流程圖右邊)(都是執(zhí)行的channelHandlerContext的 方法)(由用戶線程或者代碼發(fā)起的io操作)
1.bind 綁定本地地址事件
2.鏈接服務端事件
3.write發(fā)送事件
4.flush刷新事件
5.read讀事件
6.關閉當前channel事件
pipeline的構成>使用ServerBootStrap 或者 bootStrap啟動服務端和客戶端 netty為每一個channel都會單獨的創(chuàng)建一個獨立的pipeline
channelhandler是線程不安全的辟躏,channelpipeline是線程安全的谷扣,channelhandler支持動態(tài)的添加和刪除,:使用場景在業(yè)務高峰期的時候要對系統(tǒng)進行阻塞保護的時候捎琐,就可以動態(tài)的增減阻塞的channelhandler
channelpipeline維護了channlerhandler 名和 channelHandlerContext實例的映射關系
ChannelHandler支持注解:
1.sharable 多個channelPipeline公用一個channlehandler
2.skip被注釋的方法会涎,直接跳過
解碼的處理流程:
messageToMessageDecoder? 》 byteTomessageDecoder(都是實現channelHandler接口)
NETTY 提供的半包解碼器:
1.lineBaseFrameDecoder
2.DelimiterBasedFrameDecoder
3.LengthFieldBasedFrameDecoder :通過長度區(qū)分? 與其對應的是LengthFieldPrepender將消息編碼為 : 長度字段+原消息
如何區(qū)分整包:固定長度∪鸫眨回車換行符末秃。分隔符。指定長度
到此為止前面的問題到底是啥籽御?知道了pipeline 和 channelhandler的聯系(容器)练慕,但是channelhandlerContext和他們的聯系是啥呢?技掏?铃将??哑梳?
以下是通過資料查詢這三者的關系:
pipeline是handler的通道劲阎,context表示ChannelHandler和ChannelPipeline之間的關系
注意:一個ChannelHandler可以屬于多個ChannelPipeline,它也可以綁定多個ChannelHandlerContext實例鸠真,但是要使用注解sharable
注意二:handler線程不安全的所以要注意
什么是EventLoop和EventLoopGroup悯仙?
netty我們會創(chuàng)建boss和woker線程組帶著問題看一下?
netty框架主要的線程就是IO線程
Netty線程模型本質上遵循了Reactor的基礎線程模型弧哎,但是實現與Reactor存在差異.首先我們回顧一下Reactor的線程模型雁比,這地方我應該整理 傳送=======>
http://www.reibang.com/writer#/notebooks/48158437/notes/79347965
回顧完以后看下netty
雖然netty官方的demo是主從reactor模式但是也是支持netty用單或者多線程的模型
Netty的最佳實踐
1.創(chuàng)建兩個NioEventLoopGroup用于隔離nio Acceptor 和 nio 的io線程 (理解為一個是鏈接線程一個是讀數據的線程)
2.不要在ChannelHandler中啟動用戶線程稚虎,可以啟用線程可以將解碼后的消息發(fā)送到業(yè)務線程
3.解碼操作要在nio的解碼handler中操作撤嫩,不要切換到用戶線程中完成消息解碼
4.如果業(yè)務處理簡單可以直接在nio線程上完成業(yè)務邏輯編排
5.如果業(yè)務復雜,不要在nio線程上完成業(yè)務蠢终,要將解碼的消息封裝成task交付給業(yè)務線程中執(zhí)行序攘,盡快將nio線程釋放茴她,
推薦線程數量計算公式
線程數量 = (線程總時間/瓶頸資源時間) * 瓶頸資源的線程并行數
NioEventLoop:不單單是一個io線程,除了負責io的讀寫程奠,可以實現定時任務丈牢,可以處理系統(tǒng)task。(當io線程和用戶線程都要操作網絡資源的時候瞄沙,為防止并發(fā)操作導致的鎖競爭己沛,將用戶線程的操作task放到隊列中,有io線程同一處理距境,實現局部無鎖化)
Netty解決了空輪詢
空輪詢的bug:正常情況下selector的select操作是阻塞的申尼,只有被監(jiān)聽的fd有讀寫的時候才會被喚醒,但是空輪詢的bug是沒有 讀寫的時候也會被喚醒垫桂,所以會出現空輪詢的異常师幕,
產生的原因:linux2.6內核中, poll和epoll突然中斷連接的socket的場景下诬滩,會將exenset集合設置為pollup霹粥。或者是poerr 疼鸟。這樣就造成了evenset事件集合發(fā)生了變化后控。將selector被喚醒就出現了空輪詢
代碼如下:
Netty解決空輪詢的方式:
1.對selector的select的操作周期進行統(tǒng)計
2.每次出現空輪詢都記錄數
3.如果再某個周期內出現了n次空輪詢則證明出現了空輪詢的bug
Netty會通過重建selector進行系統(tǒng)恢復
ChannelFuture是異步獲取io操作結果的,分為兩種狀態(tài)uncompleted 和 completed 空镜。再其剛創(chuàng)建的時候處于第一種狀態(tài)忆蚀,在其完成之后會處于第二種狀態(tài),第三種狀態(tài)也分為操作成功姑裂,失敗馋袜,取消三種
Netty的架構剖析
Netty的行業(yè)應用
多線程編程在netty中的應用
拓展知識:
主流的操作系統(tǒng)提供了線程的實現,主要有三種
內核線程:這種線程有內核來完成線程的切換舶斧,內核通過線程調度器來對線程進行調度欣鳖,并負責將線程任務映射到不同的處理器上
用戶線程實現:完全建立在用戶空間的線程庫上,用戶線程的創(chuàng)建茴厉,啟動泽台,運行完全在用戶態(tài)中完成,不需要內核的幫助矾缓,因此執(zhí)行性能更改
混合實現:顧名思義
sun-jdk:在內核線程實現
solaris-jdk:可以設置參數選擇在哪里實現
Netty的邏輯架構
又上向下?
service
? ? |
pipeline
? ? |
Reactor
解釋:
Reactor通信調度層:包含ReactornioeventLoop怀酷。niosocketChannel , bytebuf嗜闻,主要負責監(jiān)聽網絡的讀寫和鏈接操作蜕依,負責將網絡層數據讀取到內存中,例如創(chuàng)建連接,讀寫事件样眠,并將事件觸發(fā)到pipeline等
ChannelPipeline職責鏈:負責動態(tài)的編排職責連友瘤。職責鏈選擇監(jiān)聽和處理自己關心的事件,可以將外部的消息轉換成自己內部的pojo對象檐束,這樣上層業(yè)務只關心業(yè)務處理即可
service業(yè)務編排層辫秧,對于業(yè)務開發(fā)人只需要關心業(yè)務邏輯制定即可,這種分層架構設計理念實現了nio框架各層之間的解耦被丧,便于上層協議棧的開發(fā)和業(yè)務邏輯定制
NETTY優(yōu)化的地方:
1.非阻塞的io庫盟戏,基于reactor實現,解決同步阻塞io模式下甥桂,服務端無法平滑增長客戶端的問題
2.tcp接收發(fā)送緩沖區(qū)用直接內存抓半,避免內存復制
3.通過內存池方式循環(huán)利用bytebuf,避免頻繁常見和銷毀bytebuf
4.環(huán)形數組緩沖區(qū)實現無鎖化并發(fā)編程
5.關鍵資源的單線程串行處理格嘁,避免多線程并發(fā)帶來的所競爭和額外的cpu資源消耗
6.通過引用計數器及時釋放不在引用的對象笛求,鎖粒度的內存管理降低了gc的頻率
netty的可靠性
檢測鏈路的有效性,由于長鏈接每次發(fā)送消息都要創(chuàng)建鏈路糕簿,性能相比于短連接性能更高探入,netty提供了兩種心跳檢測(空閑時檢測機制)
讀空閑超時機制:
當連續(xù)T周期沒有消息可讀,觸發(fā)超時handler懂诗,并發(fā)送心跳消息蜂嗽,進行鏈路檢測,當n個周期沒有收到心跳則關閉鏈路
寫空閑超時機制:
當有T個周期沒有數據要發(fā)的情況下殃恒,用戶可以基于寫空閑超時發(fā)送心跳消息植旧,進行鏈路檢測,如果連續(xù)n個周期沒有收到對方心跳离唐,主動關閉
netty的可制定性
1.責任鏈模式病附,channelpipeline基于責任鏈開發(fā),便于業(yè)務邏輯的攔截亥鬓,定制和拓展
2.基于接口的開發(fā):關鍵的類庫都提供了接口或者抽象類完沪,如果netty無法滿足用戶需求,可以由用戶自定義實現類
3.提供了工廠類嵌戈,通過重載這些工廠類可以按需創(chuàng)建出用戶實現的對象
4.提供大量系統(tǒng)參數覆积,供用戶按需設置,增強系統(tǒng)的場景定制性
Dubbo與Netty
流程圖:
DUBBO的RPC框架熟呛,通過dubbo encode方式將pojo對象編碼為dubbo協議的二進制字節(jié)流宽档,通過netty client發(fā)送給服務提供者,服務提供者的netty server從niosocketChannel讀取二進制碼流庵朝,將bytebuf解碼為dubbo請求消息并調用服務提供者吗冤,處理完后返回
netty位dubbo提供的了高性能nio框架又厉,主要職責在:
1.提供了異步高性能的nio框架
2.nio客戶端和服務端
3.心跳檢測能力
4.斷鏈重試機制
5.流量控制
6.dubbo協議的編解碼handler
NETTY CLIENT 的主要實現:
nettyhandler 持有了dubbo的自定義的channelhandler ,通過channelhandler 回調dubbo的filter欣孤,實現rpc的服務調用
學習高性能
https://www.cnblogs.com/flgb/p/13122281.html