網(wǎng)上有很多關(guān)于pos機(jī)內(nèi)存告警,長(zhǎng)連接Netty服務(wù)內(nèi)存泄漏的知識(shí),也有很多人為大家解答關(guān)于pos機(jī)內(nèi)存告警的問(wèn)題,今天pos機(jī)之家(www.www690aa.com)為大家整理了關(guān)于這方面的知識(shí),讓我們一起來(lái)看下吧!
本文目錄一覽:
pos機(jī)內(nèi)存告警
作者:京東科技 王長(zhǎng)春
背景事情要回顧到11.11備戰(zhàn)前夕,在那個(gè)風(fēng)雨交加的夜晚,一個(gè)急促的咚咚報(bào)警,驚破了電閃雷鳴的黑夜,將沉浸在夢(mèng)香,熟睡的我驚醒。
一看手機(jī)咚咚報(bào)警,不好!有大事發(fā)生了!電話?cǎi)R上打給老板:
老板說(shuō): 長(zhǎng)連接嗎?我說(shuō):是的!老板說(shuō):該來(lái)的還是要來(lái)的,最終還是來(lái)了,快,趕緊先把服務(wù)重啟下!我說(shuō):已經(jīng)重啟了!老板說(shuō): 這問(wèn)題必須給我解決了!我說(shuō):必須的!
線上應(yīng)用長(zhǎng)連接Netty服務(wù)出現(xiàn)內(nèi)存泄漏了!真讓人頭大
在這風(fēng)雨交加的夜晚,此時(shí),面對(duì)毫無(wú)頭緒的問(wèn)題,以及迫切想攻克問(wèn)題的心,已經(jīng)讓我興奮不已,手一把揉揉剛還迷糊的眼,今晚又注定是一個(gè)不眠之夜!
應(yīng)用介紹說(shuō)起支付業(yè)務(wù)的長(zhǎng)連接服務(wù),真是說(shuō)來(lái)話長(zhǎng)。
我們還是長(zhǎng)話短說(shuō)——
隨著業(yè)務(wù)及系統(tǒng)架構(gòu)的復(fù)雜化,一些場(chǎng)景,用戶操作無(wú)法同步得到結(jié)果。一般采用的短連接輪訓(xùn)的策略,客戶端需要不停的發(fā)起請(qǐng)求,時(shí)效性較差還浪費(fèi)服務(wù)器資源。
短輪訓(xùn)痛點(diǎn):
時(shí)效性差耗費(fèi)服務(wù)器性能建立、關(guān)閉鏈接頻繁相比于短連接輪訓(xùn)策略,長(zhǎng)連接服務(wù)可做到實(shí)時(shí)推送數(shù)據(jù),并且在一個(gè)鏈接保持期間可進(jìn)行多次數(shù)據(jù)推送。服務(wù)應(yīng)用常見(jiàn)場(chǎng)景:PC端掃碼支付,用戶打開(kāi)掃碼支付頁(yè)面,手機(jī)掃碼完成支付,頁(yè)面實(shí)時(shí)展示支付成功信息,提供良好的用戶體驗(yàn)。
長(zhǎng)連服務(wù)優(yōu)勢(shì):
時(shí)效性高提升用戶體驗(yàn)減少鏈接建立次數(shù)一次鏈接多次推送數(shù)據(jù)提高系統(tǒng)吞吐量這個(gè)長(zhǎng)連接服務(wù)使用Netty框架,Netty的高性能為這個(gè)應(yīng)用帶來(lái)了無(wú)上的榮光,承接了眾多長(zhǎng)連接使用場(chǎng)景的業(yè)務(wù):
PC收銀臺(tái)微信支付聲波紅包POS線下掃碼支付問(wèn)題現(xiàn)象回到線上問(wèn)題,出現(xiàn)內(nèi)存泄漏的是長(zhǎng)連接前置服務(wù),觀察線上服務(wù),這個(gè)應(yīng)用的內(nèi)存泄漏的現(xiàn)象總伴隨著內(nèi)存的增長(zhǎng),這個(gè)增長(zhǎng)真是非常的緩慢,緩慢,緩慢,2、3個(gè)月內(nèi)從30%慢慢增長(zhǎng)到70%,極難發(fā)現(xiàn):
每次發(fā)生內(nèi)存泄漏,內(nèi)存快耗盡時(shí),總得重啟下,雖說(shuō)重啟是最快解決的方法,但是程序員是天生懶惰的,要數(shù)著日子來(lái)重啟,那絕對(duì)不是一個(gè)優(yōu)秀程序員的行為!問(wèn)題必須徹底解決!
問(wèn)題排查與復(fù)現(xiàn)排查遇到問(wèn)題,毫無(wú)頭緒,首先還是需要去案發(fā)第一現(xiàn)場(chǎng),排查“死者(應(yīng)用實(shí)例)”死亡現(xiàn)場(chǎng),通過(guò)在發(fā)生FullGC的時(shí)間點(diǎn),通過(guò)Digger查詢ERROR日志,沒(méi)想到還真找到破案的第一線索:
io.netty.util.ResourceLeakDetector [176] - LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetection.level=advanced' or call ResourceLeakDetector.setLevel() See http://netty.io/wiki/reference-counted-objects.html for more information.
線上日志竟然有一個(gè)明顯的"LEAK"泄漏字樣,作為技術(shù)人的敏銳的技術(shù)嗅覺(jué),和找Bug的直覺(jué),可以確認(rèn),這就是事故案發(fā)第一現(xiàn)場(chǎng)。
我們憑借下大學(xué)四六級(jí)英文水平的,繼續(xù)翻譯下線索,原來(lái)是這吶!
ByteBuf.release() 在垃圾回收之前沒(méi)有被調(diào)用。啟用高級(jí)泄漏報(bào)告以找出泄漏發(fā)生的位置。要啟用高級(jí)泄漏報(bào)告,請(qǐng)指定 JVM 選項(xiàng)“-Dio.netty.leakDetectionLevel=advanced”或調(diào)用 ResourceLeakDetector.setLevel()
啊哈!這信息不就是說(shuō)了嘛!ByteBuf.release()在垃圾回收前沒(méi)有調(diào)用,有ByteBuf對(duì)象沒(méi)有被釋放,ByteBuf可是分配在直接內(nèi)存的,沒(méi)有被釋放,那就意味著堆外內(nèi)存泄漏,所以內(nèi)存一直是非常緩慢的增長(zhǎng),GC都不能夠進(jìn)行釋放。
提供了這個(gè)線索,那到底是我們應(yīng)用中哪段代碼出現(xiàn)了ByteBuf對(duì)象的內(nèi)存泄漏呢?項(xiàng)目這么大,Netty通信處理那么多,怎么找呢?自己從中搜索,那肯定是不靠譜,找到了又怎么釋放呢?
復(fù)現(xiàn)面對(duì)這一連三問(wèn)?別著急,Netty的日志提示還是非常完善:啟用高級(jí)泄漏報(bào)告找出泄漏發(fā)生位置嘛,生產(chǎn)上不可能啟用,并且生產(chǎn)發(fā)生時(shí)間極長(zhǎng),時(shí)間上來(lái)不及,而且未經(jīng)驗(yàn)證,不能直接生產(chǎn)發(fā)布,那就本地代碼復(fù)現(xiàn)一下!找到具體代碼位置。
為了本地復(fù)現(xiàn)Netty泄漏,定位詳細(xì)的內(nèi)存泄漏代碼,我們需要做這幾步:
1、配置足夠小的本地JVM內(nèi)存,以便快速模擬堆外內(nèi)存泄漏。如圖,我們?cè)O(shè)置設(shè)置PermSize=30M, MaxPermSize=43M
2、模擬足夠多的長(zhǎng)連接請(qǐng)求,我們使用Postman定時(shí)批量發(fā)請(qǐng)求,以達(dá)到服務(wù)的堆外內(nèi)存泄漏。
啟動(dòng)項(xiàng)目,通過(guò)JProfiler JVM監(jiān)控工具,我們觀察到內(nèi)存緩慢的增長(zhǎng),最終觸發(fā)了本地Netty的堆外內(nèi)存泄漏,本地復(fù)現(xiàn)成功:
那問(wèn)題具體出現(xiàn)在代碼中哪塊呢? 我們最重要的是定位具體代碼,在開(kāi)啟了Netty的高級(jí)內(nèi)存泄漏級(jí)別為高級(jí),來(lái)定位下:
3、開(kāi)啟Netty的高級(jí)內(nèi)存泄漏檢測(cè)級(jí)別,JVM參數(shù)如下:-Dio.netty.leakDetectionLevel=advanced
再啟動(dòng)項(xiàng)目,模擬請(qǐng)求,達(dá)到本地應(yīng)用JVM內(nèi)存泄漏,Netty輸出如下具體日志信息,可以看到,具體的日志信息比之前的信息更加完善:
2020-09-24 20:11:59.078 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler [101] - [id: 0x2a5e5026, L:/0:0:0:0:0:0:0:0:8883] READ: [id: 0x926e140c, L:/127.0.0.1:8883 - R:/127.0.0.1:58920]2020-09-24 20:11:59.078 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler [101] - [id: 0x2a5e5026, L:/0:0:0:0:0:0:0:0:8883] READ COMPLETE2020-09-24 20:11:59.079 [nioEventLoopGroup-2-8] ERROR io.netty.util.ResourceLeakDetector [171] - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.WARNING: 1 leak records were discarded because the leak record count is limited to 4. Use system property io.netty.leakDetection.maxRecords to increase the limit.Recent access records: 5#5:io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.readBytes(AdvancedLeakAwareCompositeByteBuf.java:476)io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.readBytes(AdvancedLeakAwareCompositeByteBuf.java:36)com.jd.jr.keeplive.front.service.nettyServer.handler.LongRotationServerHandler.getClientMassageInfo(LongRotationServerHandler.java:169)com.jd.jr.keeplive.front.service.nettyServer.handler.LongRotationServerHandler.handleHttpFrame(LongRotationServerHandler.java:121)com.jd.jr.keeplive.front.service.nettyServer.handler.LongRotationServerHandler.channelRead(LongRotationServerHandler.java:80)io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)......#4:Hint: 'LongRotationServerHandler#0' will handle the message from this point.io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:1028)io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:36)io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpMessage.touch(HttpObjectAggregator.java:359)......#3:Hint: 'HttpServerExpectContinueHandler#0' will handle the message from this point.io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:1028)io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:36)io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpMessage.touch(HttpObjectAggregator.java:359) ......#2:Hint: 'HttpHeartbeatHandler#0' will handle the message from this point.io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:1028)io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:36)io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpMessage.touch(HttpObjectAggregator.java:359) ......#1:Hint: 'IdleStateHandler#0' will handle the message from this point.io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:1028)io.netty.buffer.AdvancedLeakAwareCompositeByteBuf.touch(AdvancedLeakAwareCompositeByteBuf.java:36)io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpMessage.touch(HttpObjectAggregator.java:359) ......Created at:io.netty.util.ResourceLeakDetector.track(ResourceLeakDetector.java:237)io.netty.buffer.AbstractByteBufAllocator.compositeDirectBuffer(AbstractByteBufAllocator.java:217)io.netty.buffer.AbstractByteBufAllocator.compositeBuffer(AbstractByteBufAllocator.java:195)io.netty.handler.codec.MessageAggregator.decode(MessageAggregator.java:255) ......
開(kāi)啟高級(jí)的泄漏檢測(cè)級(jí)別后,通過(guò)上面異常日志,我們可以看到內(nèi)存泄漏的具體地方:com.jd.jr.keeplive.front.service.nettyServer.handler.LongRotationServerHandler.getClientMassageInfo(LongRotationServerHandler.java:169)
不得不說(shuō)Netty 內(nèi)存泄漏排查這點(diǎn)是真香!真香好評(píng)!
問(wèn)題解決找到問(wèn)題了,那我么就需要解決,如何釋放ByteBuf內(nèi)存呢?
如何回收泄漏的ByteBuf其實(shí)Netty官方也針對(duì)這個(gè)問(wèn)題做了專門(mén)的討論,一般的經(jīng)驗(yàn)法則是,最后訪問(wèn)引用計(jì)數(shù)對(duì)象的一方負(fù)責(zé)銷(xiāo)毀該引用計(jì)數(shù)對(duì)象,具體來(lái)說(shuō):
如果一個(gè)[發(fā)送]組件將一個(gè)引用計(jì)數(shù)的對(duì)象傳遞給另一個(gè)[接收]組件,則發(fā)送組件通常不需要銷(xiāo)毀它,而是由接收組件進(jìn)行銷(xiāo)毀。如果一個(gè)組件使用了一個(gè)引用計(jì)數(shù)的對(duì)象,并且知道沒(méi)有其他對(duì)象將再訪問(wèn)它(即,不會(huì)將引用傳遞給另一個(gè)組件),則該組件應(yīng)該銷(xiāo)毀它。詳情請(qǐng)看翻譯的Netty官方文檔對(duì)引用計(jì)數(shù)的功能使用:
【翻譯】Netty的對(duì)象引用計(jì)數(shù)【原文】Reference counted objects
總結(jié)起來(lái)主要三個(gè)方式:方式一:手動(dòng)釋放,哪里使用了,使用完就手動(dòng)釋放。方式二:升級(jí)ChannelHandler為SimpleChannelHandler, 在SimpleChannelHandler中,Netty對(duì)收到的所有消息都調(diào)用了ReferenceCountUtil.release(msg)。方式三:如果處理過(guò)程中不確定ByteBuf是否應(yīng)該被釋放,那交給Netty的ReferenceCountUtil.release(msg)來(lái)釋放,這個(gè)方法會(huì)判斷上下文是否可以釋放。
考慮到長(zhǎng)連接前置應(yīng)用使用的是ChannelHandler,如果升級(jí)SimpleChannelHandler對(duì)現(xiàn)有API接口變動(dòng)比較大,同時(shí)如果手動(dòng)釋放,不確定是否應(yīng)該釋放風(fēng)險(xiǎn)也大,因此使用方式三,如下:
線上實(shí)例內(nèi)存正常問(wèn)題修復(fù)后,線上服務(wù)正常,內(nèi)存使用率也沒(méi)有再出現(xiàn)因泄漏而增長(zhǎng),從線上我們?cè)黾拥娜罩局锌闯觯現(xiàn)ullHttpRequest中ByteBuf內(nèi)存釋放成功。 從此長(zhǎng)連接前置內(nèi)存泄漏的問(wèn)題徹底解決。
總結(jié)一、Netty的內(nèi)存泄漏排查其實(shí)并不難,Netty提供了比較完整的排查內(nèi)存泄漏工具
JVM 選項(xiàng) -Dio.netty.leakDetection.level
目前有 4 個(gè)泄漏檢測(cè)級(jí)別的:
DISABLED - 完全禁用泄漏檢測(cè)。不推薦。SIMPLE - 抽樣 1% 的緩沖區(qū)是否有泄漏。默認(rèn)。ADVANCED - 抽樣 1% 的緩沖區(qū)是否泄漏,以及能定位到緩沖區(qū)泄漏的代碼位置。PARANOID - 與 ADVANCED 相同,只是它適用于每個(gè)緩沖區(qū),適用于自動(dòng)化測(cè)試階段。如果生成輸出包含“LEAK:”,則可能會(huì)使生成失敗。本次內(nèi)存泄漏問(wèn)題,我們通過(guò)本地設(shè)置泄漏檢測(cè)級(jí)別為高級(jí),即:-Dio.netty.leakDetectionLevel=advanced定位到了具體內(nèi)存泄漏的代碼。
同時(shí)Netty也給出了避免泄漏的最佳實(shí)踐:
在 PARANOID 泄漏檢測(cè)級(jí)別以及 SIMPLE 級(jí)別運(yùn)行單元測(cè)試和集成測(cè)試。在 SIMPLE 級(jí)別向整個(gè)集群推出應(yīng)用程序之前,請(qǐng)先在相當(dāng)長(zhǎng)的時(shí)間內(nèi)查看是否存在泄漏。如果有泄漏,灰度發(fā)布中使用 ADVANCED 級(jí)別,以獲得有關(guān)泄漏來(lái)源的一些提示。不要將泄漏的應(yīng)用程序部署到整個(gè)群集。二、解決Netty內(nèi)存泄漏,Netty也提供了指導(dǎo)方案,主要有三種方式
方式一:手動(dòng)釋放,哪里使用了,使用完就手動(dòng)釋放,這個(gè)對(duì)使用方要求比較高了。方式二:如果處理過(guò)程中不確定ByteBuf是否應(yīng)該被釋放,那交給Netty的ReferenceCountUtil.release(msg)來(lái)釋放,這個(gè)方法會(huì)判斷上下文中是否可以釋放,簡(jiǎn)單方便。方式三:升級(jí)ChannelHandler為SimpleChannelHandler, 在SimpleChannelHandler中,Netty對(duì)收到的所有消息都調(diào)用了ReferenceCountUtil.release(msg), 升級(jí)接口,可能對(duì)現(xiàn)有API改動(dòng)會(huì)比較大。
以上就是關(guān)于pos機(jī)內(nèi)存告警,長(zhǎng)連接Netty服務(wù)內(nèi)存泄漏的知識(shí),后面我們會(huì)繼續(xù)為大家整理關(guān)于pos機(jī)內(nèi)存告警的知識(shí),希望能夠幫助到大家!
