在上一章中咱们终于用多线程把妹子图给抓下来了,可是网络环境是很不可控的,你很难判断你抓图的线程啥状况,如今是在努力干活呢,仍是在消极怠工。这一章,咱们用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数据输入进去看到的就是狂拽酷炫的报表图了,但我是一个下妹子图的,报表啥的能有妹子好看吗,我就不截图了,你们请自行前去围观吧。
最后的最后,源代码在此