一條debug日誌引發的性能問題排查

又到周天了啊!每次到周天都會有問題啊啊啊!加班,加班,呃呃呃,本來已經想好了,這個周天,紅燒老鱉,搞點豆瓣醬爆出紅油,再來點青紅二荊條什麼的,最後撒上蔥花香菜,哇!肯定好看又好吃。但是,XXX啊這個問題你要解決啊,下週大家都等着用數據。。。。。

我不知道大家知不知道?

反正我身邊的程序員一般都是特別敬崗愛業,長的特別好看,說話特別好聽,職業道德又特別高尚的人,電話秒接,信息秒回,一年365天,一天24小時,從不關機,隨叫隨到,加班不要錢,下班不早退,上班不遲到。除此之外我還是一個特別特別特別喜歡解決問題的人,有成就感啊,不然程序員這勾當,敲敲代碼,改改配置,整整文檔,接接電話,發發信息,拍拍照片,喝喝開水,上上廁所,吃吃麪條,也就太無趣了,,,

 

這原本是一個很基礎,很常規,很簡單的問題,但最近人家突然想寫寫寫寫寫了,,,,

想寫就寫吧!這是你的自由,想吃啥就做點啥吧!這也是你的自由,想熬多晚的夜就熬多晚的夜,想想多久的你就想多久的你,蒙多想去哪兒就去哪兒??????

 

其實也不會寫啦,總是前言不搭後語,邏輯混亂,思維跳躍,比如,不正在用jstack?怎麼突然又用到了strace了?爲什麼啊?

但事在人爲嘛!!!有時候努努力,還是會有個好結果的,比如早上的公交,只要你肯擠,每一輛公交你都能上,雖然有時努力也不一定會有結果,比如你再怎麼努力,你喜歡的不喜歡你的人還是不喜歡你。你知道?原來只要你肯放棄,沒有什麼是難得到你的。

人生啊!不留遺憾就好,但在遺憾中痛苦的苟活着,纔是你完美的人生,,,,,,,,,,,,

 

廢話ZJE多啊,整理下發型,言歸正傳,雖然最近剪了短髮,但該裝的B,還是一個不會拉下,,,,

整理髮型中……..

整理髮型中…………

整理髮型中…………….

整理髮型中………………

整理髮型中…………………..

整理完畢。。。。。。。。。。。。。。。

 

現場同事反應,啓動了十幾個flume的file2kafka進程去把本地目錄下的文件接入到kafka,但是程序處理數據卻極慢,一天一個進程才處理20G的文件,一個文件1G左右,排除了小文件會導致文件系統性能下降的可能。在公司大概瞭解了下現場情況,程序並沒有做大量的數據清洗動作,應該是很快的,所以起初懷疑是數據積壓太多,數據已經從pagecache落盤,一下啓動了太多進程從磁盤讀取數據,嗖一下達到了磁盤的性能瓶頸,我設想很多線程處於不可中斷的D狀態,iowait 88%,負載高達1888,服務器卡死,但到現場top了一下發現不是這麼回事,失誤,失誤

 

來來來,讓你們見識見識咱們這普普通通256G內存,48核CPU,萬兆網卡,(啥盤啊?sorry沒關注過唉)的服務器

 

top如下:

服務器一切正常,swap爲0,iowait爲0,cpu使用率也不是很高,對不起我根本沒看網絡,咱萬兆網卡還是光纖局域網啊,如果負載比較高還要用iostat vmstat dstat iotop iftop netstat lsof等命令XJB去查看磁盤,內存,網絡的具體情況

 

fullgc會導致cpu使用率突然飆升,影響程序性能,看了一會cpu使用很穩定,jstat -gcutil 確認一下fullgc基本沒有,不應該一天才處理20G的數據啊!莫不是業務代碼寫的太垃圾了???不會啊。看了下日誌程序也一直在跑,但日誌級別是debug,到這一個老司機應該就大致知道是什麼問題了

 

接下來隨便挑了一個看着順眼的pid ,top -Hp 37205看下這個進程中各個線程的情況,如下

 

這個進程內一共有67個線程,有2個正在運行,top默認是按cpu佔用率排序,有3個線程cpu佔用率較高,這個flume進程內有兩個工作線程,一個掃描目錄讀文件並進行數據處理的DirectorySource線程,一個寫數據到kafka的kafkaSink線程,由於kafka客戶端生產者接口內部還會啓動一個kafkaProducer的網絡IO線程,做實際數據的寫入,所以一共會有3個工作線程做數據處理

 

要想知道這3個線程中的那個是DirectorySource線程,那個是KafkaSink線程,那個是kafkaProducer網絡IO線程,我們還需

jstack 37205 > 37205.jstack一下

我們把第一個線程id 37521轉換成16進製得到0x9291

然後vi 37205.jstack輸入 /0x9291找到此線程的堆棧如下:

由函數調用棧可知這個線程是kafkaProducer網絡IO線程,當然用jvisualvm一下就能看出來了,在我的印象中kafka的生產者異步寫就算做gzip壓縮也是很高效的,雖然flume的KafkaSink使用的是同步寫但也不會cpu吃到84%一天才寫20G數據,同時在kafkaProducer網絡IO線程中是沒有什麼複雜的計算除了壓縮,而這個線程正在打debug日誌,問題很可能就出在了打debug日誌上,看這函數調用棧寫個日誌還真是麻煩,長長的一串讓人生厭,在函數調用棧中我們還可以看到在寫日誌時會獲取鎖做同步

咱們strace -p 37521看下還能不能再發現點什麼

 

 

可以看到kafkaProcucer網絡IO線程基本上是在獲得鎖(futex用戶空間快速鎖)和write數據,而write的大部分是日誌文件,木好意思,還少了張圖忘拍了,ll /proc/37521/fd/294或lsof |grep 37521|grep 294,可知fd 294是日誌文件,由此我們可以知道大量的write寫的是日誌文件而不是寫socket發送數據到kafka,由於寫日誌默認用的是行緩衝,每當遇到換行時就會調用系統調write,把jvm堆內存緩衝的數據寫到磁盤,如果用全緩衝,當buffer滿後再寫到磁盤,就會出現日誌刷一半顯示不全,或寫的日誌沒填滿緩衝區,tail日誌時看不到日誌的現象,體驗感極差。而且一行日誌才180字節左右,每次寫都要直接到磁盤的pagecache而不是緩衝在jvm堆內存中,大量頻繁的這種系統調用無疑又拖慢了線程的運行速度,從strace的對系統調用統計看futex比write更耗時

到這咱們其實就可以結束了,直接調整日誌級別到INFO,重啓收工,不打日誌線程就不會去頻繁的獲取鎖write文件了啊,節約下來的時間就可以用來寫數據到kafka了啊,不就可以一路狂奔了?單位時間內處理的數據不就上去了?

 

接下來咱們看下第二個線程的堆棧,37522轉16進製得0x9292

vi 37205.jstack輸入/0x9292找到此線程的堆棧如下

 

由函數調用棧我們可知此線程是flume的kafkaSink線程,並且此線程在打debug日誌做同步時沒獲取到鎖而被掛起了,打個日誌竟然被阻塞掛起了,你說你還打不打debug日誌

到這咱們其實就可以結束了,直接調整日誌級別到INFO,重啓收工,不打日誌線程就不會去頻繁的獲取鎖write文件了啊,節約下來的時間就可以用來寫數據到kafka了啊,不就可以一路狂奔了?單位時間內處理的數據不就上去了?

 

接下來咱們看下第三個線程的堆棧,37502轉16進製得0x927e

vi 37205.jstack輸入/0x927e找到此線程的堆棧如下

 

由函數調用棧可知此線程是DirectorySource線程,此線程在進行事務的commit時由於沒有獲取到資源被掛起了,而此線程的調用棧中是沒有打印debug日誌的,查看DirectorySource源碼發現在業務處理邏輯中是沒有打degug日誌,但它不應該很快?畢竟不用獲取鎖,不用write啊

 

strace看一下能發現什麼

發現此線程很少read數據98%的時間都花費在futex系統調用上

此線程cpu佔用率是11%,是3個線程中佔用最少的,此線程也是數據的源頭,如果能把此線程的cpu佔用率提高,那麼單個進程一天處理的數據就會增加

爲什麼DirecotrySource線程沒打debug日誌,但是卻在事務commit時被掛起了?

我們來看下flume數據處理的流程

 

首先source線程從外部讀取數據,調用註冊的interceptor攔截進行業務處理,然後把數據放入channel(配置的是MemoryChannel,就是一個LinkedBlockingDeque),最後sink線程從channel拿取數據輸出到外部

現在線程卡在了把數據放入channel,可以排除是業務代碼的問題,我們看下flume的MemoryChannel事務commit的代碼

 

此段代碼的大意就是,判斷此次事務commit前,sink從channel拿走的數據所佔用的空間是否可以容納此次事務commit source要放入的數據,如果不可以就tryAcquire嘗試獲取空間(其實就是等待sink取走數據)

tryAcquire代碼如下

 

當獲取不到時就會等待,這個等待時長我們配置的是10s,到此整個問題的前因後果已經很明瞭了

 

由於kafkaSink線程會頻繁的打印debug日誌,kafkaProducer網絡io線程也會頻繁的打印debug日誌,並且打日誌是行緩衝,每打一次就會獲取鎖做同步,並write到磁盤,效率很低?

 

Sorry,這個推論我十分不同意,對於write調用寫180字節每秒70-80W次不是問題,再説磁盤現在,毫無壓力,我更覺得可能是Log4j存在性能問題,你看它打個日誌,函數調用棧多深啊

 

兩個線程在爭用同一把保護日誌文件的鎖時,由於打日誌太耗時,導致鎖被長時間持有,所以kafkaSink線程有時會獲取不到鎖,從而導致kafkaSink線程會被掛起等待,進而導致kafkaSink線程從channel消費數據的速度小於DirectorySource線程生產速度,等到chnnel的剩餘空間無法容納DirectorySource線程生產的數據時,DirectorySource就只能停下來等待,如果10s內還沒有空間,就拋出異常,就出現了,這一現象,DirectorySource線程很少read數據,從strace的統計結果我們可以看到DirectorySource的futex比kafkaProducer的futex更慢,因爲要等待kafkaSink從channel取走數據釋放空間

 

 

 

OK,更改日誌級別爲INFO,重啓top如下

猜猜這次那個線程的cpu佔用率會最高?

第一個線程pid 40870轉16進製得0x9fa6 線程堆棧如下:

由函數調用棧可知這個cpu佔用率最高的線程是DirectorySource線程,處於正在運行狀態,在日誌級別是debug時這個線程的cpu佔用率是最低的

strace如下

我們看到read次數變多了,這意味着單位時間內,DirectorySource線程從外部讀取了更多的數據輸入到系統內,futex比之前快了很多,因爲不必再頻繁停下來等待channel有空間可以使用

 

第二個線程pid 40872轉16進製得0x9fa8 線程堆棧如下:

由函數調用棧可知這個線程是kafkaProducer網絡IO線程,處於正在運行狀態

第三個線程pid 40873轉16進製得0x9fa9 線程堆棧如下:

由函數調用棧可知這個線程是kafkaSink線程,由於kafkaSink在寫完一批數據到kafka並確認寫成功後纔會進行事務的commit,所以kafkaSink寫數據時是以同步的方式寫的,當寫完數據後獲取kafka ack的同步調用結果時被掛起了

DirectorySource線程cpu佔用率已經到了90%,kafkaProducer線程的cpu佔用率63%,而kafkaSink線程的cpu佔用率才37%,這是由於kafkaSink線程只是把數據放到緩衝區,數據的壓縮與發送都是kafkaProducer網絡IO線程做的,以目前的情況一個source對一個sink剛剛好,兩個source就會導致kafkaProducer網絡IO線程吃不消了

至此我們已經沒有什麼可以再調整的了,source線程的生產與sink線程的消費速度基本匹配,而且sink線程還有點點盈餘

 

那條debug日誌那去了??????

 

 

 

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