
由于代碼1處執行完後直接進入2、3,那麼netty服務端就會關閉退出。
解決一、直接在代碼1後面處加上同步阻塞sync,那麼隻有服務端正常關閉channel時才會執行下面的語句
解決二、把代碼2和3移到operationComplete裡面,那麼也隻有channel關閉時才會讓netty的兩個線程組關閉
2、netty用戶端連接配接池資源OOM
生産環境用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是比較危險而且沒有意義。
3、netty居然會産生記憶體池洩漏問題
在調用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放回池裡,否則記憶體就要爆掉。
對于記憶體池洩露可以的監控可以配置啟動參數
不同參數資訊如下:
<code>DISABLED</code> 完全關閉記憶體洩露檢測,并不建議
<code>SIMPLE</code> 以1%的抽樣率檢測是否洩露,預設級别
<code>ADVANCED</code> 抽樣率同<code>SIMPLE</code>,但顯示詳細的洩露報告
<code>PARANOID</code> 抽樣率為100%,顯示報告資訊同<code>ADVANCED</code>
最後,悄悄告訴你,網上的你些netty入門demo大都存在記憶體池洩露問題,隻不過資料量傳輸少,可能運作大半年才會出現LEAK,就連《netty權威指南》入門demo也存在這個問題,也許就隻是個入門demo,是以不弄得太複雜。什麼你不信,你可以在入門demo的TimeClientHandler或TimeServerHandler加上下面這坨代碼。
妥妥的
這就是為什麼很多人照抄網上的demo仍會出現記憶體池洩露的原因
4、Netty發送隊列積壓導緻記憶體洩漏
用戶端頻繁發送消息可以導緻發送隊列積壓,進而記憶體增大,響應時間長,CPU占用高。
此時我們可以為用戶端設定高低水位機制,防止自身隊列消息積壓
此外,除了用戶端消息隊列積壓也可能因網絡連結處理能力、伺服器讀取速度小于己方發送速度有關。是以在日常監控中,需要将Netty的鍊路數、網絡讀寫速度等名額納入監控系統,發現問題之後需要及時告警。
5、API網關高并發壓測性能波動
服務端轉發請求
結果發現記憶體和CPU占用高,同時QPS下降,停止壓測一段時間,CPU占用和記憶體下降,QPS恢複正常。
用MAT分析
得出是線程池的char[]積壓,進入老年代,導緻頻繁full gc。究其原因是,每次都建立64kb的char來存放處理消息,哪怕實際接收消息有 100位元組。修改char大小為消息大小,問題得到解決
6、為什麼Netty Server端無法收到Client端發來的消息
原因:在handler裡面是直接處理業務資訊,導緻IO的操作阻塞,無法讀取Client端發來的消息
建議将業務操作将由另一個線程處理,而不應放在IO線程裡處理
推薦線程的計算公式:
(1) 線程數量=(線程總時間/瓶頸資源時間)*瓶頸資源的線程并行數
(2)QPS=1000/線程總時間*線程數
7、Netty3.x更新到Netty4.x後産生"bug"
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,使用者需要并發保護。
8、為什麼netty自帶的業務線程池沒有提高性能,netty有bug?
server端使用netty自帶的線程池來處理業務
而client端如下
實際結果server端的QPS隻有個位數,究其原因是一個tcp連接配接對應一個channel,一個channel就對應一個DefaultEventExecutor(業務線程) 執行,是以它雖然給channel綁定線程池,但一個channel還是一個業務線程在處理。解決辦法是在ChannelHandler裡面再建立一個線程池,此時就能利用線程池的并行處理能力。
當然, server端使用netty自帶的線程池來處理業務,它的用法是當建立多個tcp連接配接時,每個連接配接能對應一個線程來處理ChannelHandler。是以它在多tcp連接配接時能提高業務的并行處理能力。
Netty提供的業務線程池能降低了鎖競争,提升了系統的并發處理性能。如果使用業務自定義實作的線程池,如果追求更高的性能,就要在消除或減輕鎖競争上下工夫(ThreadPoolExecutor采用的是“一個阻塞隊列+N個工作線程”的模型,如果業務線程數比較多,就會形成激烈的鎖競争)
9、發送端和接收端處理速度不均衡怎麼辦?
可用流量×××方案, 流量×××和流控的最大差別在于,流控會拒絕消息,流量×××不拒絕和丢棄消息,無論接收量多大,它總能以近似恒定的速度下發消息,跟變壓器的原理和功能類似。
接收端代碼如下: