在上一章中咱們終於用多線程把妹子圖給抓下來了,可是網絡環境是很不可控的,你很難判斷你抓圖的線程啥狀況,如今是在努力幹活呢,仍是在消極怠工。這一章,咱們用springboot2的actuator來監控線程池。java
在談actuator前要說明一點,springboot和springboot2在actuator的使用上有很大的不一樣,我以前搜這方面的文章,基本都是在談springboot1裏面的作法,而在springboot2中狀況發生了很大的變化,在這個很二的教程中,1裏面的事,我就不談了。咱們先了解下actuator是什麼。git
執行器(Actuator)的定義web
執行器是一個製造業術語,指的是用於移動或控制東西的一個機械裝置,一個很小的改變就能讓執行器產生大量的運動。
An actuator is a manufacturing term that refers to a mechanical device for moving or controlling something. Actuators can generate a large amount of motion from a small change.spring
看完這個高大上的定義,我不明覺厲的點點頭,說的啥啊,徹底不明吧。其實Actuator就是一個監控器,能夠用來監控應用各方面的數據,好比jvm的狀況,應用的啓動狀態,http是否通暢,線程池的狀況啊.......停,有線程池的狀態監控,太好了,要的就是這個,讓咱們開始用吧。apache
首先是在pom文件裏面把actuator加上去json
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
而後到application.properties裏面加上actuator顯示的配置瀏覽器
management.endpoints.web.exposure.include=*
這個表示全部的監控信息都經過web的形式顯示出來,就是能直接在瀏覽器看到,聰明的你確定想到兩點:tomcat
一、除了web仍是其餘形式嗎?springboot
答:有的,還有jmx的方式網絡
二、有include是否是還有exclude?
答:是的,include表示顯示的信息,exclude表示不能顯示的信息
追加問題:那有些啥能夠顯示的信息啊
答:如今講。
咱們如今能夠啓動springboot啦,而後訪問http://localhost:8080/actuator/就能看到內容啦。
這是springboot自帶的監控信息內容,其實還蠻多的,都是經過json格式輸出,點擊進去還能夠看到詳細內容,好比看health
只有一個信息,好沒有說服力啊......算了,畢竟咱們此次是爲了線程池來的,咱們看下線程池的數據怎樣,http://localhost:8080/actuator/threaddump:
{ "threads": [ { "threadName": "DestroyJavaVM", "threadId": 31, "blockedTime": -1, "blockedCount": 0, "waitedTime": -1, "waitedCount": 0, "lockName": null, "lockOwnerId": -1, "lockOwnerName": null, "inNative": false, "suspended": false, "threadState": "RUNNABLE", "stackTrace": [], "lockedMonitors": [], "lockedSynchronizers": [], "lockInfo": null }, { "threadName": "http-nio-8080-Acceptor-0", "threadId": 29, "blockedTime": -1, "blockedCount": 0, "waitedTime": -1, "waitedCount": 0, "lockName": null, "lockOwnerId": -1, "lockOwnerName": null, "inNative": true, "suspended": false, "threadState": "RUNNABLE", "stackTrace": [ { "methodName": "accept0", "fileName": "ServerSocketChannelImpl.java", "lineNumber": -2, "className": "sun.nio.ch.ServerSocketChannelImpl", "nativeMethod": true }, { "methodName": "accept", "fileName": "ServerSocketChannelImpl.java", "lineNumber": 422, "className": "sun.nio.ch.ServerSocketChannelImpl", "nativeMethod": false }, { "methodName": "accept", "fileName": "ServerSocketChannelImpl.java", "lineNumber": 250, "className": "sun.nio.ch.ServerSocketChannelImpl", "nativeMethod": false }, { "methodName": "serverSocketAccept", "fileName": "NioEndpoint.java", "lineNumber": 450, "className": "org.apache.tomcat.util.net.NioEndpoint", "nativeMethod": false }, { "methodName": "serverSocketAccept", "fileName": "NioEndpoint.java", "lineNumber": 73, "className": "org.apache.tomcat.util.net.NioEndpoint", "nativeMethod": false }, { "methodName": "run", "fileName": "Acceptor.java", "lineNumber": 95, "className": "org.apache.tomcat.util.net.Acceptor", "nativeMethod": false }, { "methodName": "run", "fileName": "Thread.java", "lineNumber": 748, "className": "java.lang.Thread", "nativeMethod": false } ], ......太長了,我看了一下,有2千多行,我就不所有列出來了
這個反差也太大了吧,線程池的數據這麼多,我對系統的線程狀況一點興趣都沒有,我只關心我本身建立的線程池好吧,若是每次都要從2千多行裏面去找個人線程池的內容,這和看不到有啥區別,看來系統提供的線程監控數據是不靠譜了。
在spring-boot-actuator-1.x的版本里面,實現一個本身的監控用的是繼承接口的方式,最經常使用的是PublicMetrics接口,但這個接口在spring-boot-actuator-2.x裏面,已經不存在了,改用註解的方式實現,我在網上找了一堆的文章,都在提醒我使用PublicMetrics接口,我愣是沒找到,幸虧我以前在搞spring statemachine的時候,已經吃過這個版本的虧了(跳坑記錄,請看這),終於靠我坑王的經驗跳過去了。
讓咱們回憶一下以前配置的線程池,咱們此次的目的就是在下圖的時候可以監控咱們的這個線程池的狀況。
package com.skyblue.crawel.config; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableAsync @Configuration class TaskPoolConfig { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //設置核心線程數 executor.setCorePoolSize(10); //設置最大線程數 executor.setMaxPoolSize(20); //線程池所使用的緩衝隊列 executor.setQueueCapacity(200); // 設置線程活躍時間(秒) executor.setKeepAliveSeconds(60); // 線程名稱前綴 executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 等待全部任務結束後再關閉線程池 executor.setWaitForTasksToCompleteOnShutdown(true); // 等待時間 (默認爲0,此時當即中止),並沒等待xx秒後強制中止 executor.setAwaitTerminationSeconds(60); return executor; } }
這個名爲taskExecutor的線程池就是抓圖的線程池,也是我須要特別監控的線程池,我須要監控的其實有幾點:
一、線程數量
二、線程狀態
三、線程詳情
讓咱們開始編碼:
import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.stereotype.Component; @Endpoint(id = "mythread") @Component public class MyThreadEndpoint { @ReadOperation public ThreadDumpDescriptor threadDump() { return new ThreadDumpDescriptor(Arrays .asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true))); } /** * A description of a thread dump. Primarily intended for serialization to JSON. */ public final class ThreadDumpDescriptor { private MyThreadDesc myThreadDesc ; private List<ThreadInfo> threads; private ThreadDumpDescriptor(List<ThreadInfo> threads) { if(myThreadDesc == null) { myThreadDesc = new MyThreadDesc(); } this.threads = threads; } public MyThreadDesc getMyThreads() { myThreadDesc.threadStatus = new HashMap<String,String>(); int i = 0; List<ThreadInfo> myThreads = new ArrayList<ThreadInfo>(); for(ThreadInfo threadInfo:this.threads) { if(threadInfo.getThreadName().indexOf("taskExecutor") > -1) { i++; myThreads.add(threadInfo); myThreadDesc.threadStatus.put(threadInfo.getThreadName(),threadInfo.getThreadState().name()); } } myThreadDesc.threads = myThreads; myThreadDesc.theadCount = i; return myThreadDesc; } } //線程信息類 private class MyThreadDesc{ //線程當前狀態 private Map<String,String> threadStatus; public Map<String, String> getThreadStatus() { return threadStatus; } public void setThreadStatus(Map<String, String> threadStatus) { this.threadStatus = threadStatus; } public int getTheadCount() { return theadCount; } public void setTheadCount(int theadCount) { this.theadCount = theadCount; } public List<ThreadInfo> getThreads() { return threads; } //當前線程數量 private int theadCount; //個人線程信息 private List<ThreadInfo> threads; } }
讓咱們同樣同樣開始講:
@Endpoint(id = "mythread") @Component
@Endpoint代表是咱們自定義的監控項,id就是名稱,最後在頁面顯示的也是這個id,@Component這個註解在系統自帶的endpoint類是沒有的,我估計是spring-boot-actuator包針對@Endpoint作了掃描,但自定義包須要先@Component註解讓應用掃描到,而後才能被actuator看到。
而後是@ReadOperation,一個自定義監控類在頁面操做的話會有三種,和http method的對應是這樣的
因爲咱們這邊只是顯示監控信息,不涉及到其餘操做,因此只用到 了@ReadOperation,若是還須要動態修改監控對象的內容,好比修改線程池大小啥的,就會用到另外兩個註解啦。
後面ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)這句咱們其實就獲取到了全部線程的信息,ThreadDumpDescriptor 主要是把咱們的線程信息過濾出來,以MyThreadDesc類的數據格式展示到前臺,裏面就包括了線程數、線程狀態和每一個線程的詳細信息。咱們運行一下,就能看到了。
點擊進去後的內容:
{ "myThreads": { "threadStatus": {}, "theadCount": 0, "threads": [] } }
啥都沒有,那是由於還沒開始下圖,我把抓圖的開起來,再看下結果
從這裏就很清楚的看到taskExccutor線程池的狀況了,每一個線程的當前狀態、線程池目前的開啓數和每一個線程的詳細信息,若是還有其餘須要,能夠很方便的自行添加。
最後說一句,若是json格式的輸出你們仍是嫌棄醜陋,還能夠用可視化的工具把它美化一下,好比用grafana,把json數據輸入進去看到的就是狂拽酷炫的報表圖了,但我是一個下妹子圖的,報表啥的能有妹子好看嗎,我就不截圖了,你們請自行前去圍觀吧。
最後的最後,源代碼在此