原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此宣告。
簡介
現如今,有兩種常見的軟體資源幾乎成了Java後端程式的標配,即執行緒池與連線池,但這些池化資源非常的重要,一旦不夠用了,就會導致程式阻塞、效能低下,所以有時我們需要看看它們的使用情況,以判斷這裡是否是瓶頸。
檢視活躍執行緒數
在Linux上,透過top -H -p 1
命令,可以檢視java程式的執行緒情況,其中1是java程式號,如下:
如上,可以看到執行緒的名稱、CPU使用率等,其中http-nio-8080-e
就是Tomcat執行緒池中的執行緒,tomcat執行緒全名類似於http-nio-8080-exec-20
,由於Linux中執行緒名稱有長度限制,所以被截斷了。
注:jdk8的話,需要jdk8u222以上版本,才能在top中看到執行緒名稱。
我們數一下http-nio-8080-e
執行緒的數量,發現它有20個,正好對應上了在springboot中的執行緒配置。
這樣能透過top得到執行緒池的執行緒數量了,但如何瞭解執行緒池的使用情況,即活躍執行緒有多少個呢?
經過檢視man文件,我發現top命令有一個-i
選項,描述如下:
意思就是i
是一個開關選項,預設會顯示全部執行緒,而開啟此選項之後,就只顯示活躍執行緒了!
所以,只需要利用-i
選項,再配合sed/awk/uniq等文字處理命令,即可以統計出活躍執行緒數了,如下:
$ top -H -i -b -d 1 -n2 -p 1 | awk -v RS= 'END{print $0}' | awk '$1 ~ /[0-9]+/{print $12}' | sed -E 's/[0-9]+/n/g' | sort | uniq -c
可以看到,20個執行緒的執行緒池中,在1秒內只有4個執行緒是活躍的,執行緒池中執行緒數量是足夠的。
這個命令指令碼就不展開解釋了,也不復雜,有linux命令基礎的將命令依次拆開執行,應該能Get到指令碼邏輯,沒學過linux命令的話,就直接拿去用吧?
檢視活躍連線數
在Linux上,使用ss -natp|grep pid=1
可以檢視1號程式的TCP連線,如下:
比如若redis資料庫埠是6379的話,那麼可這樣檢視redis連線池中連線數量,如下:
$ ss -natp | grep pid=1 | awk '$5~/:6379$/' | wc -l
20
可見當前有20個redis網路連線,那同樣的,其中有多少個是活躍的呢?
經過檢視man文件,發現ss中也有一個-i
選項,如下:
可以發現,新增-i
選項後,ss會輸出tcp連線中的一些額外資訊,其中lastsnd表示最後一次傳送包到當前所經歷的毫秒數,lastrcv表示最後一次接收包到當前所經歷的毫秒數。
有了這個資訊後,就可以透過awk過濾出lastsnd或lastrcv小於1000的tcp連線,這些連線即是1秒內活躍過的連線了,因此我又編寫了如下命令指令碼。
$ ss -natpi | sed '1!{N;s/\n//;}' | grep pid=1 | awk -v t=1000 'match($0,/lastsnd:(\w+) lastrcv:(\w+)/,a) && (a[1]<t || a[2]<t) && match($4,/(.+):(\w+)$/,s) && match($5,/(.+):(\w+)$/,d) && s[2]>=32768{print d[2]}' |sort |uniq -c |sort -nk2
8 80
3 3306
7 3307
6 6379
1 7916
如上,可以看到各連出埠的活躍連線情況,其中80是http連線池埠,3306與3307是MySQL主從庫的連線池埠,6379是redis連線池的埠。
這是java應用主動連出連線的活躍情況,那呼叫方連入java應用的呢?
其實只需要稍微調整一下awk指令碼即可,如下:
- 將
s[2]>=32768
調整為s[2]<32768
,其中32768是Linux預設的臨時埠號的分界線,可透過sysctl net.ipv4.ip_local_port_range
查詢,本地埠號大於這個值,代表是連出連線. - 將
print d[2]
調整為print s[2]
,和上面條件聯合起來,輸出的就是本地監聽埠了.
調整後,效果如下:
$ ss -natpi | sed '1!{N;s/\n//;}' | grep pid=1 | awk -v t=1000 'match($0,/lastsnd:(\w+) lastrcv:(\w+)/,a) && (a[1]<t || a[2]<t) && match($4,/(.+):(\w+)$/,s) && match($5,/(.+):(\w+)$/,d) && s[2]<32768{print s[2]}' |sort |uniq -c |sort -nk2
8 8080
可以發現,我們服務的8080埠,1秒內活躍過的連線數是8個。
注:只有當呼叫方也使用連線池時,這種方法獲取到的活躍連線數才是準確的,若呼叫方使用短連結的話,則不準確。
arthas檢視活躍執行緒數與連線數
透過上面的方法,已經可以檢視活躍執行緒數與連線數了,但有些情況下,會喪失一些細節,如下:
- top中的執行緒名會截斷,如果不同執行緒池的執行緒名前16字元一樣,則在top中無法區分。
- ss中是透過埠來區分執行緒池的,但http服務的埠號基本都是80或443,所以不同域名的http服務的連線池無法區分。
若需要分辯這些細節,還是要深入到jvm裡面來,而arthas就是一個不錯的工具,它的vmtool命令能夠獲取指定型別的Java物件,並從Java物件中獲取資訊。
以springboot為例,獲取內建tomcat執行緒池的活躍情況,如下:
# --action getInstances:表示獲取物件例項
# --classLoaderClass:指定類載入器
# --className:指定要獲取哪個類的例項
# --express:指定ognl表示式,用來從物件上獲取資訊
[arthas@1]$ vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className org.apache.tomcat.util.threads.ThreadPoolExecutor --express 'instances.{ #{"ActiveCount":getActiveCount(),"LargestPoolSize":getLargestPoolSize(),"CorePoolSize":getCorePoolSize(),"MaximumPoolSize":getMaximumPoolSize(),"QueueSize":getQueue().size(),"ThreadName":getThreadFactory().namePrefix }}' -x 2
上面其實就是透過vmtool工具,獲取到了tomcat的執行緒池物件,然後呼叫執行緒池的getActiveCount()
等方法,獲取到了活躍執行緒數?
要獲取連線池的活躍情況,也一併呈上吧,如下:
# 獲取druid連線池的使用情況
[arthas@1]$ vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className com.alibaba.druid.pool.DruidDataSource --express 'instances.{ #{"url":#this.getUrl().split("\\?")[0], "username":#this.getUsername(),"PoolingCount":#this.getPoolingCount(),"ActiveCount":#this.getActiveCount(),"MaxActive":#this.getMaxActive(),"WaitThreadCount":#this.getWaitThreadCount(),"MaxWaitThreadCount":#this.getMaxWaitThreadCount()} }' -x 2
# 獲取httpclient連線池的使用情況
[arthas@1]$ vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className org.apache.http.impl.conn.PoolingHttpClientConnectionManager --express 'instances.{ #pool=#this.pool.routeToPool.values() }' -x2
可以看到,arthas真的很方便實用,對於Java Boy來說,值得好好研究研究???