記一次開發組內課題 【 24G ng log 統計域名、uri訪問次數top10,最終程序使用內存64M、花費時間16s】

組內發佈一個課題:給一個4核、4G內存的環境,分析統計ng log 中訪問域名和uri數量的top10在沒有OOM並結果正確的前提下,比拼處理速度。
在大神幫助下、我的程序輕鬆奪冠。
程序很簡單,在寫了多版程序後分析程序效率瓶頸、如何更省內存、如何避免發生GC等問題,過程經歷難能可貴,寫一篇帖子記下來。

ng log 格式:
[02/Sep/2019:00:00:03 +0800] - 47.91.455.21 service.domain-api.com “GET /http/task/uri.json HTTP/1.1” 200 112 65 0.011 0.011 110.10.61.227:3122 200 curl/7.229.0 - service.domain-api.com 443 abb-was-web root=

有用的部分拆解如下:
【[時間]】【\t】【-】【\t】【IP】【\t】【域名】【空格空格空格空格(4個)】【"【請求方式(GET or POST)】【空格】【uri】【空格】【HTTP版本】"】【空格空格空格空格(4個)】【請求狀態】…【\n】

分析需求,程序需要實現以下幾個步驟:

  • 讀取文件(需考慮讀到半行合併、瞬時讀進程序的大小,保證不會OOM)
  • 分行(如果一次讀到一塊,需要考慮將行拆開)
  • 解析出目標字符串、統計次數
  • 文件讀完、用次數排序、打印

經過測試,單線程用BufferedInputStream讀取文件速度最快。
第一版本程序模型:
主線程單線程讀取文件,由於用byte[]讀取,長度固定,每次讀取到結尾都是半行,把結果丟到一個阻塞隊列裏,再啓動一個線程不停的從隊列裏取到結果進行半行合併處理,再分成一行一行的String丟給一個固定線程數量的線程池,線程池的每個線程用split()方法對單行拆解,將結果字符串做key放到一個ConcurrentHashMap中,用AtomicInteger做value計數,避免併發問題(此處用computeIfAbsent保證原子性)。阻塞隊列的長度可以控制程序瞬時使用的內存大小(長度*byte[]大小)。
經過測試,結果數量是對的,可執行速度並不快,打印GC日誌,觀察到頻繁GC會導致程序效率變低。

第二版本程序模型:
用nio的MappedByteBuffer分片(RandomAccessFile、MappedByteBuffer)多線程讀取。主線程先計算分片位置,分片時用RandomAccessFile 的 seek、readByte方法找到整行位置,保證每片是完整的。線程池每個線程一次性讀取整片(經測試每片128M、8線程比較快),每個線程自己進行分行、拆解域名等操作,也用ConcurrentHashMap 和 AtomicInteger 做統計。程序會快一些,但MappedByteBuffer 使用堆外內存,程序瞬時內存大約是線程數*每片大小,線程數量增加會增加線程切換協調開銷,不是很完美。GC依舊很頻繁。

思考:

  1. 哪個步驟是瓶頸:測試過1.2G文件讀取最快速度大約300ms,但是整個程序處理完需要1.6s到2s,所以,文件讀取不是瓶頸,處理解析纔是。
  2. 程序爲什會頻繁GC:加大線程數量(分片讀取)或者阻塞隊列長度(單線程讀取)甚至會發生FullGC,一次FullGC會多花1.5s,得不償失。

結論:

  1. 在解析過程中byte[] buffer沒有複用,每次new 出來一個String非常耗時也會花大量內存,做split操作會生成沒用的String對象,也超級耗時。那麼,去掉String成了首要優化點。
  2. 硬盤讀取速度是有上限的,讀取時單線程或者多線程沒有多大差異,只要將這個指標打滿即可。拆解單行操作則是cpu的工作,再想辦法讓cpu打滿,讀取、分析兩件事勢必有一個會慢一點,找到這個均衡點就會達到程序的最大速度。

第三版本程序模型:單線程讀取,buffer數組 做成一個池,每次讀取用一個,把結尾半行復制到下一個buffer,把buffer直接丟到線程池拆解分析,單個byte判斷,中間不產生String。放到map時需要先判斷一下key是否存在,那麼每次都需要生成一個key,這個key也是垃圾對象。把buffer包裝一下,用一個起始指針和一個結束指針表示有效字符,重寫equals方法,直接把buffer對象丟進map比較,節省內存。經過優化後,1.2G
文件僅需800~900ms,12G文件8s左右24G文件16s左右,而且,程序內存僅僅用64M不到,不會發生任何GC!!!

最終代碼放到Git上,以便參考:
https://github.com/tanguosheng/demo_final_save_memory

中途各種開發測試版本代碼:
https://github.com/tanguosheng/demo_final

ps:最終程序有些點爲了追求執行速度寫的並不標準,比如重寫的equals方法。

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