在Linux上檢視活躍執行緒數與連線數

扣釘日記發表於2023-03-04

原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此宣告。

簡介

現如今,有兩種常見的軟體資源幾乎成了Java後端程式的標配,即執行緒池與連線池,但這些池化資源非常的重要,一旦不夠用了,就會導致程式阻塞、效能低下,所以有時我們需要看看它們的使用情況,以判斷這裡是否是瓶頸。

檢視活躍執行緒數

在Linux上,透過top -H -p 1命令,可以檢視java程式的執行緒情況,其中1是java程式號,如下:
image_2023-03-04_20230304140014
如上,可以看到執行緒的名稱、CPU使用率等,其中http-nio-8080-e就是Tomcat執行緒池中的執行緒,tomcat執行緒全名類似於http-nio-8080-exec-20,由於Linux中執行緒名稱有長度限制,所以被截斷了。

注:jdk8的話,需要jdk8u222以上版本,才能在top中看到執行緒名稱。

我們數一下http-nio-8080-e執行緒的數量,發現它有20個,正好對應上了在springboot中的執行緒配置。
image_2023-03-04_20230304133328

這樣能透過top得到執行緒池的執行緒數量了,但如何瞭解執行緒池的使用情況,即活躍執行緒有多少個呢?

經過檢視man文件,我發現top命令有一個-i選項,描述如下:
image_2023-03-04_20230304134557
意思就是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

image_2023-03-04_20230304141731
可以看到,20個執行緒的執行緒池中,在1秒內只有4個執行緒是活躍的,執行緒池中執行緒數量是足夠的。

這個命令指令碼就不展開解釋了,也不復雜,有linux命令基礎的將命令依次拆開執行,應該能Get到指令碼邏輯,沒學過linux命令的話,就直接拿去用吧?

檢視活躍連線數

在Linux上,使用ss -natp|grep pid=1可以檢視1號程式的TCP連線,如下:
image_2023-03-04_20230304144050
比如若redis資料庫埠是6379的話,那麼可這樣檢視redis連線池中連線數量,如下:

$ ss -natp | grep pid=1 | awk '$5~/:6379$/' | wc -l
20

可見當前有20個redis網路連線,那同樣的,其中有多少個是活躍的呢?

經過檢視man文件,發現ss中也有一個-i選項,如下:
image_2023-03-04_20230304145652
可以發現,新增-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指令碼即可,如下:

  1. s[2]>=32768調整為s[2]<32768,其中32768是Linux預設的臨時埠號的分界線,可透過sysctl net.ipv4.ip_local_port_range查詢,本地埠號大於這個值,代表是連出連線.
  2. 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檢視活躍執行緒數與連線數

透過上面的方法,已經可以檢視活躍執行緒數與連線數了,但有些情況下,會喪失一些細節,如下:

  1. top中的執行緒名會截斷,如果不同執行緒池的執行緒名前16字元一樣,則在top中無法區分。
  2. 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

image_2023-03-04_20230304155857
上面其實就是透過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來說,值得好好研究研究???

相關文章