在項目壓測過程中,發現系統占用,上下文切換非常頻繁,在此記錄下調優過程,希望對后來人有所幫助。
測試方法:模擬客戶端實際操作,向服務器高並發發送數據,查看服務器的負載情況。
服務器基本配置如下
1,基本性能監控工具 top
1) top 使用方式1 top
通過top命令,java應用負載極高,系統調用極高(系統調用43% ,而用戶調用只有35%),cpu的大部分資源都被系統消耗了,說明系統某部分存在極不合理的地方。
2) top 使用2;輸入top后 按1,查看cpu各個核的使用情況
這個圖說明了 cpu使用分布情況還不錯,即程序線程池配置目前沒有出現問題,如果出現單個cpu,或者幾個cpu 100%,其他空閑的情況,說明線程分配不合理,無法充分利用cpu多核能力。
3) top 使用 3,查看到底是什么線程在忙碌top -Hp 25994
如果你仔細觀察就會發現一個有趣的現象,那就是好多線程id就像新出的人民幣一樣,是連着號的,一般來說,他們屬於一組線程。
找到忙碌的線程,那么如何才能映射到java應用中我們自己定義的線程呢?步驟如下
a. jstack 25994 >1 將java線程dump出來
b. 將上圖pid列的數字轉換成16進制 比如26157 16進制為0x662d
c. more 1 。然后搜索662d
找到了,通過線程名字,可以得出,當前網絡層目前比較忙碌。
注意:給線程起一個好名字非常重要,否則你刀磨的再快,命令玩兒的再溜,依然砍不到人。
2,vmstat命令 執行vmstat 1 20查看大約20次數據
從這個圖上,我們能夠看出至少三個方面的問題
1) 內核調用 達到50%左右,非常高
2) 上下文切換過於頻繁,每秒達90000次左右
3) 調度隊列線程積壓 此刻的系統已經非常慢了。
3,pidstat命令
查看上下文切換(pidstat 命令屬於sysstat組件的內容,需要自行安裝)。
pidstat -wt -p 25994 1 10 查看10次
第一列為線程id
第二列為主動上下文切換
第三列為被動上下文切換
一般來說,我們認為,線程被動上下文切換是正常的,而主動切換可能是發生了io、鎖等情況。對於如此頻繁的上下文切換,我們需要多dump幾次線程,看看如上的線程到底在做什么,dump方法以及操作系統線程id如何映射到java線程,參見上文top -Hp命令。
查看 java線程dump文件,得到如下有效數據
應用程序頻繁調用 netty 的 writeAndFlush 方法,從調用棧中我們看到:這個方法實際上是執行了一個系統調用,用於喚醒selectable(多重復用)阻塞線程。
netty的讀寫線程在頻繁的與操作系統交互寫數據。
綜上,我們大概得到了兩個結論:
1,應用頻繁的調用netty的writeAndFlush方法,這可能會產生大量的系統調用.
2,netty頻繁的與操作系統交互進行io操作。上文說過,io操作可能會導致線程主動切換上下文。
於是乎,兩個優化思路也呼之欲出,如果writeAndFlush方法會產出大量的中斷,那么netty還有沒有提供其他的方法?第二個問題,io操作太過頻繁,那么可不可以把消息稍微合並一下以減少io的頻繁度呢?(當然這種思路需要結合實際項目,我們項目的特點是小包特別頻繁)。沿着這兩個思路,重新查看了netty相關部分的源碼,幸運的是找到了突破口,
這兩個方法天生就是一起用的,write方法可以先把數據記到內存中,等隨后的flush操作把內存中所有的數據一次刷新到操作系統中。這種操作完全符合預期,即避免了系統調用,用完成了數據的 打包,美妙的是這種打包對應用而言是透明的。
調用改進策略
對於消息的發送,我們封裝了統一接口,如下
這個接口被大量調用,分布於應用的各個“角落”,改變每一個調用顯然是不可能的。並且,應用層是無法明確知道在何時讓消息“等一會”再統一發送出去的。換句話說,就是在不改變現有調用的情況下,將這種優化“神不知鬼不覺”的添加進去。根據我們的線程分配策略,我的解決思路是在一次線程調用結束后統一發送本次調用所有消息。即將需要發送消息的Channel先存在ThreadLocal中,然后,統一操作代碼片段如下
① 按線程存儲 channel集合
② 設置開關,有些線程不需要做合並
③ 發消息時,只需要把開啟這個功能的channel存起來即可。
④ 消息統一發送
有效代碼不超過20行,然后我們看一下結果vmstat 1
為方便觀察結果,我把上圖貼下來一起對比
系統調用,上下文切換,調用隊列三項指標都有顯著的改善,cpu使用率提升了20%左右(觀察上圖的id列,下圖的空閑百分比為10%左右,而上圖在30%左右)。
總結:操作系統自身提供的工具,有着無與倫比的威力,再結合jdk提供的幾個常用命令,如jstack(線程)、jmap(內存)、jstat(垃圾回收)等,能夠幫助快速幫助我們定位問題。一般來說,操作系統監控命令能夠幫助我們確定,應用到底有沒有問題(諸如,cpu使用率、內存占用情況、網絡、磁盤、調度隊列等等),而jdk工具能夠進一步幫助我們定位問題出現在哪(線程分配、jvm堆大小配置、等等)。