溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

netty填坑----論填坑,我是專業(yè)的

發(fā)布時間:2020-06-14 20:22:56 來源:網(wǎng)絡 閱讀:2115 作者:sq5d41a7a774d48 欄目:建站服務器

1、為什么netty服務端啟動后又無異常地自動退出?

netty填坑----論填坑,我是專業(yè)的

由于代碼1處執(zhí)行完后直接進入2、3,那么netty服務端就會關(guān)閉退出。

解決一、直接在代碼1后面處加上同步阻塞sync,那么只有服務端正常關(guān)閉channel時才會執(zhí)行下面的語句

解決二、把代碼2和3移到operationComplete里面,那么也只有channel關(guān)閉時才會讓netty的兩個線程組關(guān)閉


2、netty客戶端連接池資源OOM

生產(chǎn)環(huán)境用netty作為客戶端,為了提高性能,客戶端與服務端創(chuàng)建多條鏈路,同時客戶端創(chuàng)建一個TCP連接池。結(jié)果業(yè)務高峰期OOM

netty填坑----論填坑,我是專業(yè)的

從異常日志和線程資源占用來看,導致內(nèi)存泄漏的原因是應用創(chuàng)建了大量的EventLoopGroup線程池。這就是一個TCP連接對應一個NIO線程的模式。錯誤之在就是采用BIO模式來調(diào)用NIO通信框架,不僅沒優(yōu)化效果,還發(fā)生了OOM。

正確操作是

netty填坑----論填坑,我是專業(yè)的

注意:Bootstrap自身不是線程安全的,但執(zhí)行Bootstrap的連接操作是串行執(zhí)行的。connect方法它會創(chuàng)建一個新的NioSocketChannel,并從初始構(gòu)造的EventLoopGroup中選擇一個NioEventLoop線程執(zhí)行真正的Channel連接操作,與執(zhí)行Boostrap的線程無關(guān)。在同一個Boostrap創(chuàng)建多個客戶端連接,EventLoopGroup是共享的,這些連接共用同一個NIO線程組EventLoopGroup,當某個鏈路發(fā)生異常或關(guān)閉時,只需要關(guān)閉并釋放Channel本身即可,不能同時銷毀NioEventLoop和所在線程組EventLoopGroup,下方是錯誤代碼?

netty填坑----論填坑,我是專業(yè)的

Bootstrap不是線程安全的,因此在多個線程中并發(fā)操作Bootstrap是比較危險而且沒有意義。


3、netty居然會產(chǎn)生內(nèi)存池泄漏問題

netty填坑----論填坑,我是專業(yè)的

在調(diào)用ctx.writeAndFlush方法時,當消息發(fā)送完成,Netty會主動幫助應用釋放內(nèi)存,內(nèi)存釋放場景如下

(1)如果是堆內(nèi)存(PooledHeapByteBuf),則將HeapByteBuffer轉(zhuǎn)換成DirectByteBuffer,并釋放PooledHeapByteBuf到內(nèi)存池。

(2)如果是DirectByteBuffer,則不需要轉(zhuǎn)換,在消息發(fā)送完成后,由ChannelOutboundBuffer的remove方法負責釋放


為了在實際項目中更好地管理ByteBuf,下面分4種場景說明

(1)基于內(nèi)存池的請求ByteBuf,這類主要包括PooledDirectByteBuf和PooledHeapByteBuf,它由NioEventLoop線程在處理Channel讀操作時分配,需要在業(yè)務ChannelInboundHandler處理完請求消息后釋放(通常在解碼之后),它的釋放策略如下:

  1. ChannelInboundHandler繼承自SimpleChannelInboundHandler,實現(xiàn)它的抽象方法channelRead0,ByteBuf的釋放業(yè)務不用關(guān)心,由SimpleChannelInboundHandler負責釋放

  2. 在業(yè)務ChannelInboundHandler中調(diào)用ctx.fireChannelRead(msg),讓請求繼續(xù)向后執(zhí)行,直至調(diào)用DefaultChannelPipeline的內(nèi)部類TailContext,由它負責釋放請求信息

  3. 直接調(diào)用在channelRead方法里調(diào)用ReferenceCountUtil.release(reqMsg)

(2) 基于非內(nèi)存池的請求ByteBuf,它也是需要按照內(nèi)存池的方式釋放內(nèi)存

(3)基于內(nèi)存池的響應ByteBuf,根據(jù)之前的分析,只要調(diào)用了writeAndFlush或flush方法,在消息發(fā)送完成后都會由Netty框架進行內(nèi)存釋放,業(yè)務不需要主動釋放內(nèi)存

(4)基于非內(nèi)存池的響應ByteBuf,業(yè)務不需要主動釋放內(nèi)存


當然非內(nèi)存池也不一定要手動釋放,但最好手動釋放。Netty里有4種主力的ByteBuf,其中UnpooledHeapByteBuf底下的byte[]能夠依賴JVM GC自然回收;而UnpooledDirectByteBuf底下是DirectByteBuffer,是Java堆外內(nèi)存,除了等JVM GC,最好也能主動進行回收,否則導致內(nèi)存不足也產(chǎn)生內(nèi)存泄露的假象;而PooledHeapByteBuf和PooledDirectByteBuf,則必須要主動將用完的byte[]/ByteBuffer放回池里,否則內(nèi)存就要爆掉。


對于內(nèi)存池泄露可以的監(jiān)控可以配置啟動參數(shù)

netty填坑----論填坑,我是專業(yè)的

不同參數(shù)信息如下:?

  1. DISABLED?完全關(guān)閉內(nèi)存泄露檢測,并不建議

  2. SIMPLE?以1%的抽樣率檢測是否泄露,默認級別

  3. ADVANCED?抽樣率同SIMPLE,但顯示詳細的泄露報告

  4. PARANOID?抽樣率為100%,顯示報告信息同ADVANCED

最后,悄悄告訴你,網(wǎng)上的你些netty入門demo大都存在內(nèi)存池泄露問題,只不過數(shù)據(jù)量傳輸少,可能運行大半年才會出現(xiàn)LEAK,就連《netty權(quán)威指南》入門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);
????????}

?妥妥的

netty填坑----論填坑,我是專業(yè)的

這就是為什么很多人照抄網(wǎng)上的demo仍會出現(xiàn)內(nèi)存池泄露的原因


4、Netty發(fā)送隊列積壓導致內(nèi)存泄漏

客戶端頻繁發(fā)送消息可以導致發(fā)送隊列積壓,進而內(nèi)存增大,響應時間長,CPU占用高。

netty填坑----論填坑,我是專業(yè)的

此時我們可以為客戶端設(shè)置高低水位機制,防止自身隊列消息積壓

netty填坑----論填坑,我是專業(yè)的

netty填坑----論填坑,我是專業(yè)的

此外,除了客戶端消息隊列積壓也可能因網(wǎng)絡鏈接處理能力、服務器讀取速度小于己方發(fā)送速度有關(guān)。所以在日常監(jiān)控中,需要將Netty的鏈路數(shù)、網(wǎng)絡讀寫速度等指標納入監(jiān)控系統(tǒng),發(fā)現(xiàn)問題之后需要及時告警。


5、API網(wǎng)關(guān)高并發(fā)壓測性能波動

?服務端轉(zhuǎn)發(fā)請求

public?void?channelRead(ChannelHandlerContext?ctx,?Object?msg)?{
????????ctx.write(msg);
????????char?[]?req?=?new?char[64?*?1024];
????????executorService.execute(()->
????????{
????????????char?[]?dispatchReq?=?req;
????????????//簡單處理之后,轉(zhuǎn)發(fā)請求消息到后端服務,代碼省略
????????????try
????????????{
????????????????//模擬業(yè)務邏輯處理耗時
????????????????TimeUnit.MICROSECONDS.sleep(100);
????????????}catch?(Exception?e)
????????????{
????????????????e.printStackTrace();
????????????}
????????});
????}

結(jié)果發(fā)現(xiàn)內(nèi)存和CPU占用高,同時QPS下降,停止壓測一段時間,CPU占用和內(nèi)存下降,QPS恢復正常。

用MAT分析

netty填坑----論填坑,我是專業(yè)的

netty填坑----論填坑,我是專業(yè)的

得出是線程池的char[]積壓,進入老年代,導致頻繁full gc。究其原因是,每次都創(chuàng)建64kb的char來存放處理消息,哪怕實際接收消息有?100字節(jié)。修改char大小為消息大小,問題得到解決

netty填坑----論填坑,我是專業(yè)的


6、為什么Netty Server端無法收到Client端發(fā)來的消息?

原因:在handler里面是直接處理業(yè)務信息,導致IO的操作阻塞,無法讀取Client端發(fā)來的消息

建議將業(yè)務操作將由另一個線程處理,而不應放在IO線程里處理

推薦線程的計算公式:

(1) 線程數(shù)量=(線程總時間/瓶頸資源時間)*瓶頸資源的線程并行數(shù)

(2)QPS=1000/線程總時間*線程數(shù)


7、Netty3.x升級到Netty4.x后產(chǎn)生"bug"

1、版本升級后偶現(xiàn)服務端發(fā)送給客戶端的應答數(shù)據(jù)被篡改?

netty升級4后,線程模型發(fā)生變化,響應消息的編碼由NioEventLoop線程異步執(zhí)行,業(yè)務線程返回。這時如果編碼操作在修改應答消息的業(yè)務邏輯后執(zhí)行,則運行結(jié)果錯誤,數(shù)據(jù)被篡改。


2、升級后為什么上下文丟失問題?

Netty4修改了outbound的線程模型,正好影響了業(yè)務消息發(fā)送時的上下文傳遞,最終導致業(yè)務線程變量丟失


3、升級后沒有像官方描述那樣性能得到提升,反而下降了?

可將耗時的反序列操作放到業(yè)務線程里,而不是ChannelHandler,因為Netty4只有一個NioEventLoop線程來處理這個操作,業(yè)務耗時ChannelHandler被I/O線程串行執(zhí)行,所以執(zhí)行效率低。Netty3在消息發(fā)送線程模型上,充分利用業(yè)務線程的并行編碼和ChanelHandler的優(yōu)勢,在一個周期T內(nèi)可以處理N條業(yè)務消息。

性能優(yōu)化建議:適當高大work線程組的線程數(shù)(NioEventLoopGroup),分擔每個NioEventLoop線程的負載,提升ChannelHandler執(zhí)行的并發(fā)度。同時,將業(yè)務上耗時的操作從ChannelHandler移除,放入業(yè)務線程池處理。對于不合適轉(zhuǎn)移到業(yè)務線程處理的一些耗時邏輯,也可以通過為ChannelHandler綁定線程池的方式提升性能。Netty3的Downstream由業(yè)務線程執(zhí)行,意味著某一時刻有多個業(yè)務線程同時操作ChannelHandler,用戶需要并發(fā)保護。


8、為什么netty自帶的業(yè)務線程池沒有提高性能,netty有bug?

server端使用netty自帶的線程池來處理業(yè)務

netty填坑----論填坑,我是專業(yè)的

而client端如下

netty填坑----論填坑,我是專業(yè)的

實際結(jié)果server端的QPS只有個位數(shù),究其原因是一個tcp連接對應一個channel,一個channel就對應一個DefaultEventExecutor(業(yè)務線程)?執(zhí)行,所以它雖然給channel綁定線程池,但一個channel還是一個業(yè)務線程在處理。解決辦法是在ChannelHandler里面再創(chuàng)建一個線程池,此時就能利用線程池的并行處理能力。

netty填坑----論填坑,我是專業(yè)的

當然,?server端使用netty自帶的線程池來處理業(yè)務,它的用法是當建立多個tcp連接時,每個連接能對應一個線程來處理ChannelHandler。所以它在多tcp連接時能提高業(yè)務的并行處理能力。

Netty提供的業(yè)務線程池能降低了鎖競爭,提升了系統(tǒng)的并發(fā)處理性能。如果使用業(yè)務自定義實現(xiàn)的線程池,如果追求更高的性能,就要在消除或減輕鎖競爭上下工夫(ThreadPoolExecutor采用的是“一個阻塞隊列+N個工作線程”的模型,如果業(yè)務線程數(shù)比較多,就會形成激烈的鎖競爭)


9、發(fā)送端和接收端處理速度不均衡怎么辦?

可用流量×××方案, 流量×××和流控的最大區(qū)別在于,流控會拒絕消息,流量×××不拒絕和丟棄消息,無論接收量多大,它總能以近似恒定的速度下發(fā)消息,跟變壓器的原理和功能類似。

接收端代碼如下:

????????//?配置服務端的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();
????????????//?等待服務端監(jiān)聽端口關(guān)閉
????????????f.channel().closeFuture().sync();
????????}?finally?{
????????????//?優(yōu)雅退出,釋放線程池資源
????????????bossGroup.shutdownGracefully();
????????????workerGroup.shutdownGracefully();
????????}


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI