netty nio踩坑實例

主要內容

一 項目背景&技術選型

二 設計開發&遇到的問題

三 問題排查&分析

四 netty nio底層原理

五 解決方案&總結

一 項目背景&技術選型

Elasticsearch的深度分頁查詢非常耗時且十分消耗性能,自5.1.1版本後最大隻能支持1萬條數據的分頁,如果想獲取全部數據需要通過scroll(滾動)查詢的方式,因此開發了爲scroll查詢單獨提供了查詢方式。
數據傳輸方式:考慮到使用方和搜索服務器端可能存在大量數據傳輸的業務需求(如批量導出數據),如上圖所示,用戶端和服務器端採用netty的socket通信。通信步驟如下:
(1)       使用方首先通過rpc請求獲取服務器端通信的基本配置
(2)       用戶端通過socket向服務器端提交查詢參數
(3)       服務器端解析查詢參數,執行scroll查詢,並將數據通過socket通信,傳輸到客戶端。發送的每條數據中均帶有結束符標識位,用來標識是否迭代完成(此時標識符爲false)。

(4)   當服務器端迭代完畢時,發送數據,並將結束標識位設置爲true.客戶端接收到此數據,關閉與服務器端的鏈接,導出數據完畢。

基本概念
1.同步  每個請求必須逐個地被處理,一個請求的處理會導致整個流程的暫時等待,這些事件無法併發地執行。用戶線程發起I/O請求後需要等待或者輪詢內核I/O操作完成後才能繼續執行(A、B、C幾個請求,必須順序執行,執行完A才能執行B,執行完B才能執行C)
2.異步多個請求可以併發地執行,一個請求或者任務的執行不會導致整個流程的暫時等待。用戶線程發起I/O請求後仍然繼續執行,當內核I/O操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數.(A、B、C幾個請求可以同時執行,內核執行完後,通知用戶線程)
3.阻塞  某個請求發出後,由於該請求操作需要的條件不滿足,請求操作一直阻塞,不會返回,直到條件滿足(辦理業務:一直排隊等待,調用會一直阻塞到讀寫完成才返回 )

4.非阻塞 請求發出後,若該請求需要的條件不滿足,則立即返回一個標誌信息告知條件不滿足,而不會一直等待。一般需要通過循環判斷請求條件是否滿足來獲取請求結果。 eg.辦理業務:抽號後就可以做其他事,如果你等不急,可以去問工作人員到你了沒,如果沒到你就不能辦理業務。(如果不能讀寫,調用會馬上返回,當IO事件分發器會通知可讀寫時再繼續進行讀寫,不斷循環直到讀寫完成)

IO編程模型。分爲:BIO、僞異步IO、NIO、AIO(不做介紹)。

1.同步阻塞(BIO, Blocking IO)最簡單的一種IO模型,用戶線程在進行IO操作的時候通常是個系統調用,用戶線程會由用戶空間進入內核空間,內核空間數據包準備好後會將數據拷貝到用戶空間,這個時候線程在用戶態繼續執行。

爲每個客戶端請求創建一個線程進行處理,處理完成後,線程銷燬,是典型的一請求應答模型模型。

舉例:你去火車站接人,一直在火車站門口等着,直到你想接的人出來。缺陷:一個人去接還好,很多人去接火車站就會堵的水泄不通【線程開闢的太多,消耗系統資源】

2.僞異步I/O編程模型。與BIO一致,爲了解決線程創建的問題,後端採用線程池進行優化。

優點:無論客戶端有多少併發請求量,服務器端都使用n個線程(n爲線程池的活躍線程數)來處理和響應,降低了線程的消耗避免資源耗盡和宕機。

缺陷:當客戶端發送消息緩慢或者網絡傳輸較慢,正在活躍線程執行的任務會被阻塞(讀取輸入流是阻塞的),後續的IO消息會在隊列中排隊,隊列堆滿後,新任務會阻塞入隊。後續所有的新的客戶端連接都將超時。

3.NIO通信模型 (IO多路複用技術)

舉例:火車站裏面專門找了一個管理員【此時相當於單線程,reactor角色】。每個接站的人(handler)需要在管理員這裏註冊,每當有人到站【相當於io事件到達】管理員就會打電話通知人來接【相當於事件派發】。

3.NIO通信序列圖

4.幾種IO模型比較

·由於NIO模型可靠性和吞吐量均較高,性能上與AIO相差不多,故選擇NIO模型。

·使用java原生NIO模型API複雜,,類庫繁雜,存在bug,自己開發可維護性差等情況。而netty健壯性、可拓展性、性能可靠性強,經歷成百上千商業項目驗證。搜索系統自身也集成了netty,故使用nettyNIO作爲技術模型進行開發。

2.1 設計開發

   最初的版本按照netty權威指南官方demo來進行開發,設計類如下:

二、設計開發&遇到的問題

服務器端寫入EsServerHandler(模擬)

客戶端EsClientHandler接收數據方式(模擬):

三、問題排查過程

使用抓包神器wireshark

    客戶端在收到若干條數據後,線程掛住,無法收到服務器端的數據。使用wireshark抓包後,發現服務器端不再發送數據,排除是客戶端程序的問題。

正常的數據發送過程

       使用wireshark對服務器進行遠程抓包(linux需要安裝winpcap作爲代理,安裝方式:http://haohetao.iteye.com/blog/786545)發現服務器端無任何數據發出。

三、問題排查過程

將問題定位至服務器端:使用虛擬機監控和故障處理工具對服務器端進行排查。

  top命令發現服務器端進程CPU飆升,且佔用內存較大(RES-SHR=8G)。

使用top -Hp pid(top -Hp 4366 )查看哪些線程佔用CPU資源高

排查線程佔用CPU高的原因

任取一個佔用cpu較高的線程,將線程id轉爲16進制。printf “%x\n” tid

jstack命令查看CPU利用率高的線程正在做什麼。jstack pid |grep tid 

統計GC線程數量爲8個,發現所有CPU高的線程均爲GC線程。

統計GC線程數量:

查看GC情況

 使用GC工具,每隔兩秒輸出一次,查看GC情況。發現Old分區被佔滿,FullGc頻繁,且每次GC完成後Old分區佔用率依然爲100%。

·查看GC原因.發現每次GC原因均爲上次GC失敗

·查看堆內存佔用情況。使用jmap -heap pid命令發現 Old Gen和eden 分區均被佔滿

使用內存映像工具jmap 查看堆的佔用情況(jmap -histo pid |less ),發現大量的對象均爲buffer對象。

三、問題分析

問題分析:
1.內存中含有大量緩存的數據,而數據又無法發出,CPU被打滿。當操作系統load較高的時候,操作系統很有可能來不及處理低優先級的網絡IO事件,這就導致socket無法發出數據。

2.CPU幾乎被打滿的原因爲FULL GC頻繁,而jmap內存映像工具又告訴我們,系統中有大量的緩存待發送的數據。我們可以理解爲服務器端生產數據一方爲生產者,而服務器端發送socket一方爲消費者,當生產者生產速度遠大於消費者消費速度時,數據就會在內存中積壓。這些積壓的數據,最開始在eden分區,經過幾次young GC時在s0 和s1區之間來回拷貝,最終進入Old Gen。這些數據並沒有被socket發送出去,也就意味着依然會有引用指向他們。因此根據可達性分分析算法,每次FULL GC都不將老年代的垃圾進行有效的回收。這也是導致FULL GC頻繁根本原因。

參考:(https://blog.csdn.net/zhoudaxia/article/details/26093321)

3.根據上述推斷,我認爲數據發送方式存在問題。

四、netty nio底層原理

netty中的坑:  ctx.writeAndFlush的確是將數據直接發送到socket中,根據TCP IP協議,當數據發送到socket的send buffer時,後續的發送過程都不需要我們去處理。但是當socket的發送速度低於writeAndFlush時,socket的send buffer會被佔滿。當該buffer被佔滿後,ctx.writeAndFlush而會將數據發送到netty的緩存ChannelOutBondBuffer中(如jmap工具結果所示),大量的數據會在heap中進行積壓,從而導致Full GC 頻繁,socket無法發出數據。

知識點:TCP協議能夠保證數據通信的完整性。TCP/IP協議中是有消息重傳機制的,如果由於網絡等原因客戶端無法正常收到數據,服務器端會根據ack信息對數據進行重傳,只有當客戶端確認收到消息後,纔會從socket的緩存中刪除(與MQ的ack機制非常相似)。參考鏈接:http://www.ruanyifeng.com/blog/2017/06/tcp-protocol.html

五、問題解決方式

解決方式:調用writeAndFlush()方法前先判斷當前Channel的可寫入狀態,若狀態爲可寫入說明待發送數據量並未堆積超限,可以進行異步發送;否則需要執行sync()方法等待發送數據真正成功寫入socket的TCP發送緩衝區,然後在執行下一次的write操作,防止write速率過快導致內存溢出。

參考鏈接:http://yueyemaitian.iteye.com/blog/2102544

上述同步部分應有優化空間,可以採用監聽事件形式,寫入tcp緩存後,回調監聽事件,再次投遞下調數據。

五、總結。

netty的API簡單,開發門檻較低,功能強大且性能較高。Netty修復了JDK已有的NIO bug,降低了我們開發的難度,但是使用簡單也就意味着包含大量隱藏的坑,這些坑僅靠參考官方demo無法避免,必須理解底層實現原理才能解決。
網上有大量netty源碼分析文檔,本人在開發中也參考了其中一部分,感觸頗深,願大家多讀源碼,從此不踩坑。
                                                                                                 ——by 大邦

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章