监控维度
- 第一,提供健康检测接口。传统采用 ping 的方式对应用进行探活检测并不准确。有的时候,应用的关键内部或外部依赖已经离线,导致其根本无法正常工作,但其对外的 Web 端口或管理端口是可以 ping 通的。我们应该提供一个专有的监控检测接口,并尽可能触达一些内部组件。
- 第二,暴露应用内部信息。应用内部诸如线程池、内存队列等组件,往往在应用内部扮演了重要的角色,如果应用或应用框架可以对外暴露这些重要信息,并加以监控,那么就有可能在诸如 OOM 等重大问题暴露之前发现蛛丝马迹,避免出现更大的问题。
- 第三,建立应用指标 Metrics 监控。Metrics 可以翻译为度量或者指标,指的是对于一些关键信息以可聚合的、数值的形式做定期统计,并绘制出各种趋势图表。这里的指标监控,包括两个方面:一是,应用内部重要组件的指标监控,比如 JVM 的一些指标、接口的 QPS等;二是,应用的业务数据的监控,比如电商订单量、游戏在线人数等。
Spring Actuator监控
- 配置如下:
management.server.port=8081
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/admin
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
大部分端点提供的是只读信息,比如查询 Spring 的 Bean、ConfigurableEnvironment、定时任务、SpringBoot 自动配置、Spring MVC 映射等;少部分端点还提供了修改功能,比如优雅关闭程序、下载线程 Dump、下载堆 Dump、修改日志级别等。
Spring Boot Admin,它把大部分 Actuator 端点提供的功能封装为了 Web UI。
Spring Boot Actuator 帮我们预先实现了诸如数据库、InfluxDB、Elasticsearch、Redis、RabbitMQ 等三方系统的健康检测指示器 HealthIndicator。通过 Spring Boot 的自动配置,这些指示器会自动生效。当这些组件有问题的时候,HealthIndicator 会返回 DOWN 或 OUT_OF_SERVICE 状态,health 端点 HTTP 响应状态码也会变为 503,我们可以以此来配置程序健康状态监控报警。
1、实现一个单接口健康检查
@Component
@Slf4j
public class UserServiceHealthIndicator implements HealthIndicator {
@Resource
private RestTemplate restTemplate;
@Override
public Health health() {
long begin = System.currentTimeMillis();
long userId = 1L;
User user = null;
try {
user = restTemplate.getForObject("http://localhost:8080//v1/api/user/1", User.class);
if (user != null) {
return Health.up()
.withDetail("user", user)
.withDetail("time", System.currentTimeMillis() - begin)
.build();
} else {
return Health.down()
.withDetail("time", System.currentTimeMillis() - begin)
.build();
}
} catch (Exception ex) {
log.warn("health check failed!", ex);
return Health.down(ex).withDetail("time", System.currentTimeMillis() - begin).build();
}
}
}
可以看到接口耗时及响应的结果:
2、线程池聚合监控
- 定义线程池:
public class ThreadPoolProvider {
private static ThreadPoolExecutor demoThreadPool = new ThreadPoolExecutor(
1,
1,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadFactoryBuilder().setNameFormat("demo-threadpool-%d").build()
);
private static ThreadPoolExecutor ioThreadPool = new ThreadPoolExecutor(
10,
50,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("io-threadpool-%d").build()
);
public static ThreadPoolExecutor getDemoThreadPool(){
return demoThreadPool;
}
public static ThreadPoolExecutor getIOThreadPool() {
return ioThreadPool;
}
}
- 配置需要监控指标
public class ThreadPoolHealthIndicator implements HealthIndicator {
private ThreadPoolExecutor threadPool;
public ThreadPoolHealthIndicator(ThreadPoolExecutor threadPoolExecutor) {
this.threadPool = threadPoolExecutor;
}
@Override
public Health health() {
Map<String, Integer> detail = new HashMap<>();
detail.put("queue_size", threadPool.getQueue().size());
detail.put("queue_remaining", threadPool.getQueue().remainingCapacity());
if (threadPool.getQueue().remainingCapacity() > 0) {
return Health.up().withDetails(detail).build();
} else {
return Health.down().withDetails(detail).build();
}
}
}
@Component
public class ThreadPoolsHealthContributor implements CompositeHealthContributor {
private Map<String, HealthContributor> contributors = new HashMap<>();
ThreadPoolsHealthContributor() {
contributors.put("demoThreadPool", new ThreadPoolHealthIndicator(ThreadPoolProvider.getDemoThreadPool()));
contributors.put("ioThreadPool", new ThreadPoolHealthIndicator(ThreadPoolProvider.getDemoThreadPool()));
}
@Override
public HealthContributor getContributor(String name) {
return contributors.get(name);
}
@Override
public Iterator<NamedContributor<HealthContributor>> iterator() {
return contributors.entrySet().stream().map((entry) ->
NamedContributor.of(entry.getKey(), entry.getValue())).iterator();
}
}
3、向外暴露数据
- 使用 info 的 HTTP 端点
以上面线程池为例, 可以对外展示当前线程池状态:
@Component
public class ThreadPoolInfoContributor implements InfoContributor {
private static Map threadPoolInfo(ThreadPoolExecutor threadPool) {
java.util.Map<String, Object> info = new HashMap<>();
info.put("poolSize", threadPool.getPoolSize());//当前池大小
info.put("corePoolSize", threadPool.getCorePoolSize());//设置的核心池大小
info.put("largestPoolSize", threadPool.getLargestPoolSize());//最大达到过
info.put("maximumPoolSize", threadPool.getMaximumPoolSize());//设置的最大
info.put("completedTaskCount", threadPool.getCompletedTaskCount());//总
return info;
}
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("demoThreadPool", threadPoolInfo(ThreadPoolProvider.getDemoThreadPool()));
builder.withDetail("ioThreadPool", threadPoolInfo(ThreadPoolProvider.getIOThreadPool()));
}
}
- JMX MBean
注:开启JMX[spring.jmx.enabled=true]
其他监控
- 日志 Logging
- 指标 Metrics 和追
- 全链路追踪 Tracing