您好,登錄后才能下訂單哦!
由于代碼1處執行完后直接進入2、3,那么netty服務端就會關閉退出。
解決一、直接在代碼1后面處加上同步阻塞sync,那么只有服務端正常關閉channel時才會執行下面的語句
解決二、把代碼2和3移到operationComplete里面,那么也只有channel關閉時才會讓netty的兩個線程組關閉
生產環境用netty作為客戶端,為了提高性能,客戶端與服務端創建多條鏈路,同時客戶端創建一個TCP連接池。結果業務高峰期OOM
從異常日志和線程資源占用來看,導致內存泄漏的原因是應用創建了大量的EventLoopGroup線程池。這就是一個TCP連接對應一個NIO線程的模式。錯誤之在就是采用BIO模式來調用NIO通信框架,不僅沒優化效果,還發生了OOM。
正確操作是
注意:Bootstrap自身不是線程安全的,但執行Bootstrap的連接操作是串行執行的。connect方法它會創建一個新的NioSocketChannel,并從初始構造的EventLoopGroup中選擇一個NioEventLoop線程執行真正的Channel連接操作,與執行Boostrap的線程無關。在同一個Boostrap創建多個客戶端連接,EventLoopGroup是共享的,這些連接共用同一個NIO線程組EventLoopGroup,當某個鏈路發生異常或關閉時,只需要關閉并釋放Channel本身即可,不能同時銷毀NioEventLoop和所在線程組EventLoopGroup,下方是錯誤代碼?
Bootstrap不是線程安全的,因此在多個線程中并發操作Bootstrap是比較危險而且沒有意義。
在調用ctx.writeAndFlush方法時,當消息發送完成,Netty會主動幫助應用釋放內存,內存釋放場景如下
(1)如果是堆內存(PooledHeapByteBuf),則將HeapByteBuffer轉換成DirectByteBuffer,并釋放PooledHeapByteBuf到內存池。
(2)如果是DirectByteBuffer,則不需要轉換,在消息發送完成后,由ChannelOutboundBuffer的remove方法負責釋放
為了在實際項目中更好地管理ByteBuf,下面分4種場景說明
(1)基于內存池的請求ByteBuf,這類主要包括PooledDirectByteBuf和PooledHeapByteBuf,它由NioEventLoop線程在處理Channel讀操作時分配,需要在業務ChannelInboundHandler處理完請求消息后釋放(通常在解碼之后),它的釋放策略如下:
ChannelInboundHandler繼承自SimpleChannelInboundHandler,實現它的抽象方法channelRead0,ByteBuf的釋放業務不用關心,由SimpleChannelInboundHandler負責釋放
在業務ChannelInboundHandler中調用ctx.fireChannelRead(msg),讓請求繼續向后執行,直至調用DefaultChannelPipeline的內部類TailContext,由它負責釋放請求信息
直接調用在channelRead方法里調用ReferenceCountUtil.release(reqMsg)
(2) 基于非內存池的請求ByteBuf,它也是需要按照內存池的方式釋放內存
(3)基于內存池的響應ByteBuf,根據之前的分析,只要調用了writeAndFlush或flush方法,在消息發送完成后都會由Netty框架進行內存釋放,業務不需要主動釋放內存
(4)基于非內存池的響應ByteBuf,業務不需要主動釋放內存
當然非內存池也不一定要手動釋放,但最好手動釋放。Netty里有4種主力的ByteBuf,其中UnpooledHeapByteBuf底下的byte[]能夠依賴JVM GC自然回收;而UnpooledDirectByteBuf底下是DirectByteBuffer,是Java堆外內存,除了等JVM GC,最好也能主動進行回收,否則導致內存不足也產生內存泄露的假象;而PooledHeapByteBuf和PooledDirectByteBuf,則必須要主動將用完的byte[]/ByteBuffer放回池里,否則內存就要爆掉。
對于內存池泄露可以的監控可以配置啟動參數
不同參數信息如下:?
DISABLED
?完全關閉內存泄露檢測,并不建議
SIMPLE
?以1%的抽樣率檢測是否泄露,默認級別
ADVANCED
?抽樣率同SIMPLE
,但顯示詳細的泄露報告
PARANOID
?抽樣率為100%,顯示報告信息同ADVANCED
最后,悄悄告訴你,網上的你些netty入門demo大都存在內存池泄露問題,只不過數據量傳輸少,可能運行大半年才會出現LEAK,就連《netty權威指南》入門demo也存在這個問題,也許就只是個入門demo,所以不弄得太復雜。什么你不信,你可以在入門demo的TimeClientHandler或TimeServerHandler加上下面這坨代碼。
????????ByteBuf?firstMessage?=?null;???????? ????????for?(int?j?=?0;?j?<?Integer.MAX_VALUE;?j++)?{ ????????????firstMessage?=?Unpooled.buffer(1024);???????????? ????????????for?(int?i?=?0;?i?<?firstMessage.capacity();?i?++)?{ ????????????????firstMessage.writeByte((byte)?i); ????????????} ????????????ctx.writeAndFlush(firstMessage); ????????}
?妥妥的
這就是為什么很多人照抄網上的demo仍會出現內存池泄露的原因
客戶端頻繁發送消息可以導致發送隊列積壓,進而內存增大,響應時間長,CPU占用高。
此時我們可以為客戶端設置高低水位機制,防止自身隊列消息積壓
此外,除了客戶端消息隊列積壓也可能因網絡鏈接處理能力、服務器讀取速度小于己方發送速度有關。所以在日常監控中,需要將Netty的鏈路數、網絡讀寫速度等指標納入監控系統,發現問題之后需要及時告警。
?服務端轉發請求
public?void?channelRead(ChannelHandlerContext?ctx,?Object?msg)?{ ????????ctx.write(msg); ????????char?[]?req?=?new?char[64?*?1024]; ????????executorService.execute(()-> ????????{ ????????????char?[]?dispatchReq?=?req; ????????????//簡單處理之后,轉發請求消息到后端服務,代碼省略 ????????????try ????????????{ ????????????????//模擬業務邏輯處理耗時 ????????????????TimeUnit.MICROSECONDS.sleep(100); ????????????}catch?(Exception?e) ????????????{ ????????????????e.printStackTrace(); ????????????} ????????}); ????}
結果發現內存和CPU占用高,同時QPS下降,停止壓測一段時間,CPU占用和內存下降,QPS恢復正常。
用MAT分析
得出是線程池的char[]積壓,進入老年代,導致頻繁full gc。究其原因是,每次都創建64kb的char來存放處理消息,哪怕實際接收消息有?100字節。修改char大小為消息大小,問題得到解決
原因:在handler里面是直接處理業務信息,導致IO的操作阻塞,無法讀取Client端發來的消息
建議將業務操作將由另一個線程處理,而不應放在IO線程里處理
推薦線程的計算公式:
(1) 線程數量=(線程總時間/瓶頸資源時間)*瓶頸資源的線程并行數
(2)QPS=1000/線程總時間*線程數
1、版本升級后偶現服務端發送給客戶端的應答數據被篡改?
netty升級4后,線程模型發生變化,響應消息的編碼由NioEventLoop線程異步執行,業務線程返回。這時如果編碼操作在修改應答消息的業務邏輯后執行,則運行結果錯誤,數據被篡改。
2、升級后為什么上下文丟失問題?
Netty4修改了outbound的線程模型,正好影響了業務消息發送時的上下文傳遞,最終導致業務線程變量丟失
3、升級后沒有像官方描述那樣性能得到提升,反而下降了?
可將耗時的反序列操作放到業務線程里,而不是ChannelHandler,因為Netty4只有一個NioEventLoop線程來處理這個操作,業務耗時ChannelHandler被I/O線程串行執行,所以執行效率低。Netty3在消息發送線程模型上,充分利用業務線程的并行編碼和ChanelHandler的優勢,在一個周期T內可以處理N條業務消息。
性能優化建議:適當高大work線程組的線程數(NioEventLoopGroup),分擔每個NioEventLoop線程的負載,提升ChannelHandler執行的并發度。同時,將業務上耗時的操作從ChannelHandler移除,放入業務線程池處理。對于不合適轉移到業務線程處理的一些耗時邏輯,也可以通過為ChannelHandler綁定線程池的方式提升性能。Netty3的Downstream由業務線程執行,意味著某一時刻有多個業務線程同時操作ChannelHandler,用戶需要并發保護。
server端使用netty自帶的線程池來處理業務
而client端如下
實際結果server端的QPS只有個位數,究其原因是一個tcp連接對應一個channel,一個channel就對應一個DefaultEventExecutor(業務線程)?執行,所以它雖然給channel綁定線程池,但一個channel還是一個業務線程在處理。解決辦法是在ChannelHandler里面再創建一個線程池,此時就能利用線程池的并行處理能力。
當然,?server端使用netty自帶的線程池來處理業務,它的用法是當建立多個tcp連接時,每個連接能對應一個線程來處理ChannelHandler。所以它在多tcp連接時能提高業務的并行處理能力。
Netty提供的業務線程池能降低了鎖競爭,提升了系統的并發處理性能。如果使用業務自定義實現的線程池,如果追求更高的性能,就要在消除或減輕鎖競爭上下工夫(ThreadPoolExecutor采用的是“一個阻塞隊列+N個工作線程”的模型,如果業務線程數比較多,就會形成激烈的鎖競爭)
可用流量×××方案, 流量×××和流控的最大區別在于,流控會拒絕消息,流量×××不拒絕和丟棄消息,無論接收量多大,它總能以近似恒定的速度下發消息,跟變壓器的原理和功能類似。
接收端代碼如下:
????????//?配置服務端的NIO線程組 ????????EventLoopGroup?bossGroup?=?new?NioEventLoopGroup(); ????????EventLoopGroup?workerGroup?=?new?NioEventLoopGroup(); ????????try?{ ????????????ServerBootstrap?b?=?new?ServerBootstrap(); ????????????b.group(bossGroup,?workerGroup) ????????????????????.channel(NioServerSocketChannel.class) ????????????????????.option(ChannelOption.SO_BACKLOG,?100) ????????????????????.handler(new?LoggingHandler(LogLevel.INFO)) ????????????????????.childHandler(new?ChannelInitializer<SocketChannel>()?{ ????????????????????????@Override ????????????????????????public?void?initChannel(SocketChannel?ch) ????????????????????????????????throws?Exception?{ ????????????????????????????ch.pipeline().addLast("Channel?Traffic?Shaping",?new?ChannelTrafficShapingHandler(1024?*?1024,?1024?*?1024,?1000)); ????????????????????????????ByteBuf?delimiter?=?Unpooled.copiedBuffer("$_" ????????????????????????????????????.getBytes()); ????????????????????????????ch.pipeline().addLast( ????????????????????????????????????new?DelimiterBasedFrameDecoder(2048?*?1024, ????????????????????????????????????????????delimiter)); ????????????????????????????ch.pipeline().addLast(new?StringDecoder()); ????????????????????????????ch.pipeline().addLast(new?TrafficShapingServerHandler()); ????????????????????????} ????????????????????}); ????????????//?綁定端口,同步等待成功 ????????????ChannelFuture?f?=?b.bind(port).sync(); ????????????//?等待服務端監聽端口關閉 ????????????f.channel().closeFuture().sync(); ????????}?finally?{ ????????????//?優雅退出,釋放線程池資源 ????????????bossGroup.shutdownGracefully(); ????????????workerGroup.shutdownGracefully(); ????????}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。