版本说明:
本文档基于Promtheus-2.34.0.windows + alertmanager-0.24.0.windows + grafana-8.5.2.windows +SpringBoot2.4.9
主要实现功能:
1. 操作系统/中间件/SpringBoot项目常用基本指标监控和展示
2. SpringBoot自定义指标监控
3. 报警信息发送SpringBoot处理
一、整体架构
1、监控架构图
2、各模块功能描述
-
Prometheus:监控主体,通过从
jobs
/exporter
获取监控度量数据(pull/push两种方式,短时任务使用push); -
Grafana:监控数据展示,可以直接导入官方丰富的监控模板快速生成监控大盘;
-
Exporter:开放被监控目标的对应监控端口,Exporter的实例为Target,Target通过对应的Exporter启动,例如
linux_exporter
、MySQL_exporter
,Spring项目
也可构建作为一个Exporter; -
Alertmanager:报警模块,当在Prometheus配置文件中配置的规则被触发后,会推送报警信息到Alertmanager,然后由Alertmanager实现对应的报警方式,例如邮件、WebHook、钉钉机器人
二、安装与配置
本文档以Windows为例,Linux除安装包不同但基本同理
1、Prometheus
- 解压
prometheus-2.34.0.windows-amd64.zip
,修改prometheus.yml
:
# 此片段指定的是prometheus的全局配置, 比如采集间隔,抓取超时时间等.
global:
# 抓取间隔 默认一分钟
scrape_interval: 15s
# 抓取超时时间 默认10s
scrape_timeout: 10s
# 评估规则间隔 默认一分钟
evaluation_interval: 15s
# 此片段指定报警规则文件, prometheus根据这些规则信息,触发对应的报警
rule_files:
# 规则文件需要在该配置文件同级目录手动创建
- "rules/host_rules.yml"
# - "first_rules.yml"
# - "second_rules.yml"
# 配置Targets
scrape_configs:
# Prometheus
- job_name: "prometheus"
# 默认监控端口,无需修改
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
# Prometheus自身地址
- targets: ["192.168.1.81:9090"]
# windows_exporter
- job_name: "windows"
static_configs:
- targets: ["192.168.1.81:9182"]
# MySQL_exporter
- job_name: "mysql"
static_configs:
- targets: ["192.168.1.81:9104"]
# SpringBoot_exporter
- job_name: "huishan"
# 因为该项目配置监控密码,需要在application.yml中配置用户名密码通过认证
# spring.security.user.name=admin
# spring.security.user.password=admin
basic_auth:
username: admin
password: admin
scrape_interval: 10m
# 抓取端点
metrics_path: "/actuator/prometheus"
static_configs:
# 目标路径 支持集群拉取
- targets: ["127.0.0.1:8182"]
# 一个独立监控报警模块,负责接收alertmanager报警数据,与主业务模块解耦
- job_name: "housealert"
scrape_interval: 5s
metrics_path: "/actuator/prometheus"
static_configs:
- targets: ["127.0.0.1:9088"]
# Websocket_exporter
- job_name: "websocket"
static_configs:
- targets: ["192.168.1.81:9802"]
#其他exporter(nginx、oracle、elasticsearch等)同理
# 配置alertmanager实例
alerting:
alertmanagers:
- static_configs:
- targets: ["127.0.0.1:9093"]
- 保存,建议第一次启动使用cmd,如果配置文件格式问题会直接报错并展示格式错误信息,双击
prometheus.exe
会闪烁一下消失,则同样格式错误报错。
同目录下的amtool.exe工具提供检查配置文件格式的功能
- 进入http://localhost:9090/,展示所有配置的Target及状态。
当需要清除时序数据库出现脏数据时,可使用HTTP API |普罗米修斯 提供的API进行删除,使用API之前需要将Prometheus重启并在启动命名增加后缀
–web.enable-lifecycle
,默认情况下API调用为关闭状态当然,如果是清空所有数据,最快的办法是新起一个Prometheus,将配置文件和Rules复制过去
本文档不涉及
PushGateway
的使用,可参照Pushing metrics | Prometheus
2、Grafana
grafana8.5.2存在无法导入JSON文件的问题,换用8.5.9可解决
- 解压
grafana-8.5.2.windows-amd64.zip
,执⾏./bin/grafana-server.exe
,访问http://localhost:3000/,创建账号密码。
- 保存后即可完成,效果图:
-
接下来导入模板,模板下载地址,左侧DataSource选择Prometheus,选择相应的模板,可以选择复制模板id或者下载模板Json导入
- 推荐中文模板id:
windows:10467
JVM:12856(模板首页-需要项目增加对应依赖)
- 自定义仪表可以通过模仿模板编写,编写完成后可以导出为JSON文件放到生成环境一键生成
将Grafana页面嵌入应用
设置grafana-8.5.2\conf\defaults.ini
# 允许嵌入iframe allow_embedding = true #允许匿名访问 enabled = true
重启即可
3、Alertmanager
- 解压
alertmanager-0.24.0.windows-amd64.zip
,修改alertamanger.yml
global:
# ResolveTimeout是alertmanager在警报发生时使用的默认值 默认五分钟
# 不包括EndsAt,在此时间过后,如果警报尚未更新,则可以将其声明为已解决。
# 这对来自普罗米修斯的警报没有影响,因为它们总是包括EndsAt。
# 经过此时间后,如果尚未更新告警,则将告警声明为已恢复。(即prometheus没有向alertmanager发送告警了)
resolve_timeout: 10m
# 邮件相关配置
smtp_smarthost: 'smtp.qq.com:465'
smtp_from: '发送人@foxmail.com'
smtp_auth_username: '发送人@foxmail.com'
# 如果是QQ邮箱,密码则需要从QQ邮箱网页-设置-授权码获取
smtp_auth_password: 'password'
# 默认SMTP TLS要求,Go不支持到远程SMTP端点的未加密连接 默认为true
smtp_require_tls: false
# 根路由器 对promethues发送来的警报信息进行分流匹配 到对应的接受者
route:
group_by: ['alertname']
# 第一组告警发送通知需要等待的时间,这种方式可以确保有足够的时间为同一分组获取多个告警,然后一起触发这个告警信息。
group_wait: 10s
# 发送第一个告警后,等待"group_interval"发送一组新告警。
group_interval: 10s
# 分组内发送相同告警的时间间隔。内存警报若1分钟内发送6次 参数为1分钟 则发送一次
repeat_interval: 1m
# 默认接收者名称
receiver: 'webhook'
# 子路由-这里配置的参数可以覆盖根路相同参数
routes:
- match:
owner: a
severity: page
receiver: webhook
group_wait: 10s
- match:
owner: b
severity: warning
receiver: mail
# 正则匹配
- match_re:
# 这里可以匹配出标签含有service=foo1或service=foo2或service=baz的告警
service: ^(foo1|foo2|baz)$
receiver: mail
# 接收者列表(报警实现方式)
receivers:
- name: 'webhook'
webhook_configs:
# 下面的url是自定义springboot项目中接口的访问url地址
- url: 'http://127.0.0.1:9099/housealert/alert/demo'
- name: 'mail'
email_configs:
- to: '1945192314@qq.com'
send_resolved: true
headers:
subject: "[operations] 报警邮件"
from: "监控报警中心"
to: "指挥官"
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
- 双击exe启动,如果闪屏消失则为配置文件语法错误,报错信息查看方式与Prometheus相同,进入Alertmanager,则启动成功。
当需要实现多报警和多报警方式 一对一或一对多方式时 需要如下图所示进行配置
如图所示 在
规则.yml
文件下,一个报警项在配置labels
时可以增加自定义标签,或者使用severity()
标签,在配置报警路由分发时,通过match
进行匹配指定选用的接受者receiver
,匹配方式也可以使用match_re
正则方式实现,在报警需求不复杂的情况下,建议使用一个标签匹配,即可实现一个规则的触发使用对应一个或多个报警方式实现报警。
4、Exporter
(1)windows_exporter
a. 双击启动windows_exporter-0.18.1-amd64.exe
b. 浏览器打开 localhost:9182/metrics 看到文本则启动成功
(2)linux_exporter
(占位符)
(3)mysql_exporter(以win版为例)
a. 解压mysqld_exporter-0.14.0.windows-amd64
b. 在该目录下新建my.cnf
,建议单独创建一个MySQL用户用于监控
[client]
host=192.10.110.231
port=3308
user=root
password=root
c. 双击mysqld_exporter.exe
启动
(4)webscoket_exporter
a. 设置windows系统变量WEBSOCKET_EXPORTER_URI=ws://192.168.1.19:7777/urtc
b. 命令行管理员执行windows_exporter-0.18.1-amd64.exe
c. 监控地址 127.0.0.1:9802
写入prometheus.yml
d. 监控指标参照:A Blackbox Websocket Uptime Exporter for Prometheus
(3)oracle_exporter
(占位符)
三、报警规则
1、规则配置
- 上文在
prometheus.yml
中配置了rule的文件地址rules/host_rules.yml
,在prometheus.yml
同级目录创建对应文件:
# 参考样式
groups:
# 报警组组名称
- name: hostStatsAlert
#报警组规则
rules:
#告警名称,需唯一
- alert: hostCpuUsageAlert
#promQL表达式
expr: 100 - (avg by (instance) (irate(windows_cpu_time_total{job="windows",mode="idle"}[5m])) * 100) > 0.5
#满足此表达式持续时间超过for规定的时间才会触发此报警
for: 1m
labels:
#严重级别
severity: page
# 自定义标签
owner: a
annotations:
#发出的告警标题
summary: "实例 {{ $labels.instance }} CPU 使用率过高"
#发出的告警内容
description: "实例{{ $labels.instance }} CPU 使用率超过 85% (当前值为: {{ $value }})"
- alert: hostMemUsageAlert
expr: 100.0 - 100 * windows_os_physical_memory_free_bytes{job=~"windows"} / windows_cs_physical_memory_bytes{job=~"windows"} > 0.1
# 持续时间为1min则报警
for: 1m
labels:
severity: page
owner: b
annotations:
summary: "实例 {{ $labels.instance }} 内存使用率过高"
description: "实例 {{ $labels.instance }} 内存使用率 85% (当前值为: {{ $value }})"
- 保存后重启Prometheus,http://localhost:9090/rules,查看上述配置的规则,出现则说明配置成功。配置文件中的PromQL(expr)表达式可以在Prometheus的Graph界面运行一下,检验是否符合需要。
- 进入报警页面[http://localhost:9090/alerts,规则图例:
2、检验报警
- 进入http://127.0.0.1:9093/,出现如下图所示则说明Prometheus报警生效并推送到alertmanager
四、SpringBootExporter
1、SpringBoot接入监控
<!--spring boot监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>0.15.0</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_pushgateway</artifactId>
<version>0.15.0</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.yml修改配置
management:
server:
# 对应Prometheus.yml中,job_name为SpringBoot项目的targets地址
port: 8182
address: 127.0.0.1
# 关闭监控端口身份验证,或者在Prometheus.yml中配置basic_auth
security:
enabled: false
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: "*"
metrics:
export:
# 开启Prometheus
prometheus:
enabled: true
jmx:
enabled: true
tags:
application: huishanhouse
- 在上文已在prometheus.yml中配置该项目,启动项目,浏览器进入删除配置的地址端口
-
Grafana导入模板
12856
查看效果:建议将prometheus、alertmanager和rule规则文件放入项目中使用git管理和备份,方便监控更新和回退
2、自定义监控
-
接口调用监控
//参考代码 package com.lulu.housealert.aspect; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.time.LocalDate; import java.util.concurrent.TimeUnit; /** * 监控接口调用切面类 * * @author lulu * @date 2022/7/13 */ @Aspect @Component public class ControllerMetcisAspect { @Autowired private MeterRegistry builder; @Pointcut("execution(* com.lulu.housealert.controller..*.*(..))") public void controllerPointcut() { } @Around("controllerPointcut()") public Object MetricsCollector(ProceedingJoinPoint joinPoint) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String userId = StringUtils.hasText(request.getParameter("userId")) ? request.getParameter("userId") : "no userId"; String appId = StringUtils.hasText(request.getParameter("appId")) ? request.getParameter("appId") : "no appId"; // 获取api url String api = request.getServletPath(); // 获取请求方法 String method = request.getMethod(); long timeMillis = System.currentTimeMillis(); LocalDate now = LocalDate.now(); //数组大小按实际需要创建,不能存在空值,tags为空会存现报错或不生效 String[] tags = new String[6]; tags[0] = "api"; tags[1] = api; tags[2] = "method"; tags[3] = method; tags[4] = "day"; tags[5] = now.toString(); tags[6] = "appId"; tags[7] = appId; tags[8] = "userId"; tags[9] = userId; // 请求次数加1 //自定义的指标名称:http_request_test_all,指标包含数据 builder.getMeters(); builder.counter("http_request_test_all", tags).increment(); Object object; try { object = joinPoint.proceed(); } catch (Exception e) { // 请求失败次数加1 builder.counter("http_request_test_error", tags).increment(); throw e; } finally { long f = System.currentTimeMillis(); long l = f - timeMillis; //记录请求响应时间 builder.timer("http_request_test_time", tags).record(l, TimeUnit.MILLISECONDS); } return object; } }
项目重启后随意调用接口,即可在http://url:ip/actuator/prometheus中看到
http_request_test_all
等指标,根据指标编写对应的PromQL实现监控和报警 -
自定义指标监控
-
本模块首先需要了解Prometheus提供的四种指标类型:
Counter(计数器)
、Gauge(仪表盘)
、Histogram(直方图)
、Summary(摘要)
。-
Counter:只增不减,方便用于计数
-
Gauge:可增可见的仪表盘,方便用于展示实时状态,比如可用内存
-
Histogram:直方图,比如CPU平均使用率、接口平均相应时间
-
Summary:记录平均大小,需要两个信息count(事件发生次数)和sum(所有事件的总大小)
-
-
使用度量指标实现自定义指标
-
开箱使用方式(仅展示gauge)
//伪代码 import io.micrometer.core.instrument.MeterRegistry; @Autowired private MeterRegistry builder; //方法中使用-创建指标名称为print 标签为设备id-设备所属单位的指标 //需要注意的是 Tag参数需要存在键值对应 否则会在启动报错 //Caused by: java.lang.IllegalArgumentException: size must be even, it is a set of key=value pairs List<Object> print = builder.gauge("print", Tags.of("TagA", "TagA01"), new ArrayList<>(), List::size); //赋值 最后指标展示的是集合的size() print.add(1); //new ArrayList<>()可能导致该指标的数值为NaN,如果展示状态变化,可以规避使用0作为状态
该方式创建度量指标的好处在于,Tag可以自定义生成。
-
基于注册表使用
-
首先在启动类注入指标MeterRegister
package com.lulu.housealert; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class HousealertApplication { public static void main(String[] args) { SpringApplication.run(HousealertApplication.class, args); } @Bean MeterRegistryCustomizer<MeterRegistry> configurer(@Value("${spring.application.name}") String applicationName) { return (registry) -> registry.config().commonTags("application", applicationName); } }
-
创建指标构造类,在项目启动时即可构造所有所需要的指标,适合Tag固定的情况下使用
//参考代码 package com.lulu.housealert.actuator; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; /** * 设备状态度量构造器 * * @author lulu * @date 2022/7/18 */ @Component @Data public class DeviceStatusMetricBuilder { /** * 监控指标 */ private List<Object> printStatus; private List<Object> doorStatus; private List<Object> cardStatus; private final MeterRegistry registry; @Autowired public DeviceStatusMetricBuilder(MeterRegistry registry) { this.registry = registry; } @PostConstruct private void init() { //打印机状态-仪表构造器 printStatus = registry.gauge("print_status", Tags.of("device_status", "print_status"), new ArrayList<>(), List::size); //门禁状态-仪表构造器 doorStatus = registry.gauge("door_status", Tags.of("device_status", "door_status"), new ArrayList<>(), List::size); //读卡卡状态-仪表构造器 cardStatus = registry.gauge("card_status", Tags.of("device_status", "card_status"), new ArrayList<>(), List::size); } }
/** * 业务代码使用 */ @Resource private DeviceStatusMetricBuilder builder; //从构造器中获取度量指标 List<Object> printStatus = builder.getPrintStatus(); List<Object> doorStatus = builder.getDoorStatus(); List<Object> cardStatus = builder.getCardStatus(); //清空度量操作 printStatus.clear(); doorStatus.clear(); cardStatus.clear(); //设备状态赋值 printStatus.add(1); doorStatus.add(1); cardStatus.add(1);
-
-
-
五、自定义报警
本章节介绍使用SpringBoot作为Alertmanager的
receiver
接受者,方便使用Java实现报警操作具体实现方式是编写一个Controller接口,用来接收alertmanager传来的POST请求
//参考代码
package com.lulu.housealert.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 监控接口-对接alertmanager
*
* @author lulu
* @date 2022/7/11 10:01
*/
@Slf4j
@RestController
@RequestMapping("/alert")
public class AlertController {
/**
* 接受Alertmanager发送的POST请求
*
* @修改时间 2022/7/11
* @param json
* @return
*/
@PostMapping(value = "/demo", produces = "application/json;charset=UTF-8")
public String demo(@RequestBody String json) {
log.error("---1.接收alertmanager传入数据---");
Map<String, Object> result = new HashMap<>();
result.put("msg", "报警失败");
result.put("code", 0);
if (StringUtils.isBlank(json)) {
log.error("---2.传入数据为空,报警失败---");
return JSON.toJSONString(result);
}
log.error("---2.报警数据传入---", json);
JSONObject jo = JSON.parseObject(json);
JSONObject commonAnnotations = jo.getJSONObject("commonAnnotations");
String status = jo.getString("status");
if (commonAnnotations == null) {
return JSON.toJSONString(result);
}
String subject = commonAnnotations.getString("summary");
String content = commonAnnotations.getString("description");
result.put("subject", subject);
result.put("content", content);
log.info("---3.打印告警信息", result);
System.out.println(result+"-----");
return result.toString();
}
}
//alertmanager发送JSON参考,可以按需拿取需要的数据
{
"receiver":"webhook",
"status":"firing",
"alerts":[
{
"status":"firing",
"labels":{
"alertname":"hostMemUsageAlert",
"instance":"192.168.1.81:9182",
"job":"windows",
"owner":"b",
"severity":"page"
},
"annotations":{
"description":"实例 192.168.1.81:9182 内存使用率 85% (当前值为: 84.79673195230218)",
"summary":"实例 192.168.1.81:9182 内存使用率过高"
},
"startsAt":"2022-07-22T01:57:46.438Z",
"endsAt":"0001-01-01T00:00:00Z",
"generatorURL":"http://DESKTOP-T7LHFKQ:9090/graph?g0.expr=100+-+100+%2A+windows_os_physical_memory_free_bytes%7Bjob%3D~%22windows%22%7D+%2F+windows_cs_physical_memory_bytes%7Bjob%3D~%22windows%22%7D+%3E+0.1\u0026g0.tab=1",
"fingerprint":"6a6fb2477f9f7793"
}
],
"groupLabels":{
"alertname":"hostMemUsageAlert"
},
"commonLabels":{
"alertname":"hostMemUsageAlert",
"instance":"192.168.1.81:9182",
"job":"windows",
"owner":"b",
"severity":"page"
},
"commonAnnotations":{
"description":"实例 192.168.1.81:9182 内存使用率 85% (当前值为: 84.79673195230218)",
"summary":"实例 192.168.1.81:9182 内存使用率过高"
},
"externalURL":"http://DESKTOP-T7LHFKQ:9093",
"version":"4",
"groupKey":"{}:{alertname=\"hostMemUsageAlert\"}",
"truncatedAlerts":0
}
使用时请改成自身测试环境
参考资料: