背景
微服务背景下,团队中的各同学如果有一种先见之明的超能力,能感知到自己的负责的业务健康状态,那很多问题都可以提前介入定位、去解决。但根据什么来判断呢?如果都是靠问运维同学,那问的同学多了,运维直接一天啥也不干了,况且我相信大家也不希望用这样的方式。运维嫌麻烦,重复的问题反复答,开发嫌不智能,每次都要问。那有没有一种方案能让大家都省心呢?答案是肯定的,如果有一个数据大盘,那大家都能释放出来做该做的事。
调研
总体来说还是分自研和找一些市面上存在的组件,采集自己需要的各种维度(应用层面、数据层面)的数据,由于这些数据多数情况下都相同,一些优秀的组件便应运而生了,如Grafana、InfluxDb、Nagios、Zabbix、Elastic Stack :
Grafana:Grafana是一种流行的开源数据可视化工具,可以与多个数据源集成,包括Prometheus。它可以通过可视化仪表板展示和分析Prometheus收集的数据。
InfluxDB:InfluxDB是一种开源时间序列数据库,专门用于处理和存储大量时间序列数据,如机器指标、事件日志等。和Prometheus类似,InfluxDB也具备数据采集和查询功能。
Nagios:Nagios是一种广泛使用的开源网络监控系统,可以监测网络设备、服务器和应用程序的运行状况。与Prometheus不同的是,Nagios主要基于主动式监控,而不是Prometheus的基于抓取的方式。
Zabbix:Zabbix是一种功能强大的开源网络监控系统,可以监控各种网络设备、服务器和应用程序。它提供了多种监控方式,包括主动和被动式监控,并支持自定义的监控和警报设置。
Elastic Stack:Elastic Stack(前身是ELK Stack)是一个集成了Elasticsearch、Logstash和Kibana的解决方案,能够处理和分析大量的日志和指标数据。Elasticsearch可以用于存储和查询数据,Logstash用于数据采集和清洗,Kibana则提供了数据可视化和仪表板功能。
既然上面很多都提到了Prometheus,加上团队有的同学例会也提过,既然出现了人传人的现象,那我们就选Prometheus,这样遇到问题也许解决成本会低一点。
Prometheus
结合起来看感觉作者取名有点故意的意思。具体来说究竟是干啥的呢?我们老规矩官网(https://prometheus.io/)看看一句话概述的中心思想:
译:一款在指标和报警方面领先的开源监控解决方案,让你通过指标去洞察你的系统。
动手
a. 安装:去 https://prometheus.io/download/ 根据操作系统下载对应的Prometheus版本,我还是大家熟知的穷鬼,下载windows版本玩玩儿就好了。
然后解压编辑里面的配置文件,也可以先不动,直接启动找找感觉:
Doc或PowerShell运行可执行文件:
// 也可以不带配置文件,默认是这个,除非你的名字变了
prometheus.exe --config.file=prometheus.yml
配置文件中默认是9090,所以我们直接访问:http://localhost:9090 就可以看到Prometheus服务启动了:
b. 配置:打开Prometheus.yaml 修改配置,增加你本机的应用端口,这里要注意,1处是Prometheus对自己的监控,不能修改,要新增,我刚开始没注意就报错了,希望你绕道而行:
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
#metrics_path: 'actuator/prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["localhost:9090"]
// 以下是我增加的 2个服务
- job_name: "your_server_name_one"
metrics_path: 'actuator/prometheus'
static_configs:
- targets: ["localhost:8083"]
- job_name: "your_server_name_two"
metrics_path: 'actuator/prometheus'
static_configs:
- targets: ["localhost:8198"]
整合
a.前置:Prometheus是监控系统,数据是通过扩展 SpringBoot 而来。你如果有SpringBoot项目,可以访问自带的:http://${ip}:${port}/${context-path}/actuator/health 接口看看返回的数据。如下图:左侧,监控返回了磁盘空间、ping、nacos、mail等指标,右侧是对应的数据结构:
// 如果其中某个指标不想受监控 可以通过以下配置关闭 ,
// ${jar}文件中meat_info下的文件的spring-configuration-metadata.json 查看
management.health.mail.enabled=false
management.health.diskspace.enabled=false
b. 项目集成:
增加maven依赖,我没加版本,会自动随着项目parent下载对应版本:
<!-- spring-boot-actuator依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- prometheus依赖 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- 上面2个就够了,加下面这个是为了把nacos注册也加到监控中,否则不行 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
修改项目配置文件,增加相应的Promethues配置项:
spring.application.name = ${application_name}
server.servlet.context-path = ${app_context_path}
// 指定promethues采集数据的接口
spring.cloud.nacos.discovery.metadata.metricpath=${server.servlet.context-path}/actuator/prometheus
spring.cloud.nacos.discovery.metadata.management.context-path=${server.servlet.context-path}/actuator
management.metrics.export.prometheus.enabled=true
management.metrics.export.jmx.enabled=true
management.metrics.tags.application=${spring.application.name}
如果你用的是nacos,yaml可以通过工具(https://toyaml.com/index.html)转换配置格式:
启动promethues服务,验证是否纳入管理:
上面我的服务断了,重新启动访问指定的接口查看指标,发现有很多(也可以自定义指标),随便截个图,如下:
对比发现:actuator/health接口只提供了当前服务的状态,可按需扩展,自行编码实现更多维度的健康检查,数据放置在一个Map结构中;prometheus 直接提供了诸如http请求状态码、QPS、内存等方面的数据,经团队讨论,扩展health产生的数据不便于运维同学统计,基于prometheus 更能节约时间也符合团队当前想要监控的纬度,所以最终确定监控以prometheus提供的数据为主,在各维度提供监控报警。
自定义指标
实际工作中,难免会出现除上述Prometheus默认在很多监控的指标外,还需要自定义一些指标,比如一个请求的执行耗时、JVM堆中类的实例数量和占用空间大小。
查看源码发现在io.micrometer.core.instrument.MeterRegistry下有很多子类,要操作Prometheus向其中注入数据自然要拿到该类型才可以。
观察已有的指标发现Prometheus中有指标名称、所属应用、指标值,指标标识等信息:
为了让自定义的指标也能清楚表示出所属应用,需要先将向容器注入一个Bean:
public class MicroMeterConfig {
private String springApplicationName;
MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer(MeterRegistry meterRegistry) {
//全局标明所有指标归属
return registry -> meterRegistry.config().commonTags("application", springApplicationName);
}
}
http耗时:
假如我们现在想统计一下:收到请求到返回响应结果这段时间的后端逻辑处理时间,那我们可以定义一个工具类:
public class MicroMeterTool {
private MeterRegistry meterRegistry;
//存储某个URI对应的耗时
private final ConcurrentHashMap<String, AtomicLong> appAccessTimeGaugeMap = new ConcurrentHashMap<>();
/**
* 统计某个URI的业务耗时
* @param uri 做一个AOP拦截请求 获取 URI
* @param time output:System.currentTimeMillis()-input:System.currentTimeMillis()
*/
public void appAccessTimeGauge(String uri,Long time) {
log.debug("appAccessTimeGauge uri:{} time:{}",uri,time);
//自定义指标名称
String uk ="http_req_cost_time";
AtomicLong val = appAccessTimeGaugeMap.get(uri);
if (null != val) {
val.set(time);
return;
}
try {
val = new AtomicLong(time);
meterRegistry.gauge(uk, Arrays.asList(Tag.of("uri",uri)),val,AtomicLong::get);
appAccessTimeGaugeMap.put(uri, val);
} catch (Exception e) {
log.error("metrics appAccessTimeGauge error,key:{},method:{},time:{}", uk, val, e);
}
}
}
上述代码块在业务需要的调用调用一下就好,后续准备专门写一篇AOP切面拦截http请求耗时,这里就当是定义出指标,后续会集成。以下是自定义指标展示图:
JVM:
与http耗时一样,我们同样可以写2个方法用于写入JVM实例数大小和字节占用大小。二者思路一致:都用一个Map容器来装类对应实例数或字节码,然后不断忘容器中写,需要注意的是meterRegistry会与这个Map关联,Map中key对应的value变更后自动会在Prometheus中刷新,所以注入meterRegistry只需一次,无需每次执行meterRegistry.gauge(..)。
private final ConcurrentHashMap<String, AtomicLong> jvmByteGaugeMap = new ConcurrentHashMap<>();
public void jvmUsageByteGauge(String key,Long bytes) {
log.debug("jvmUsageByteGauge key:{} bytes:{}",key,bytes);
String uk ="app_jvm_bytes";
AtomicLong val = jvmByteGaugeMap.get(key);
if (null != val) {
val.set(bytes);
return;
}
//以下代码每个类之跑一次,后续自动与Map关联
try {
val = new AtomicLong(bytes);
meterRegistry.gauge(uk, Arrays.asList(Tag.of("name", "jvm_usage"), Tag.of("type", "bytes"),Tag.of("class_name",key)),val,AtomicLong::get);
jvmByteGaugeMap.put(key, val);
} catch (Exception e) {
log.error("metrics jvmByteGaugeMap error,key:{},method:{},time:{}", uk, val, e);
}
}
private final ConcurrentHashMap<String, AtomicLong> jvmInstanceGaugeMap = new ConcurrentHashMap<>();
public void jvmUsageInstanceGauge(String key,Long instances) {
log.debug("jvmUsageInstanceGauge key:{} instances:{}",key,instances);
String uk ="app_jvm_instances";
AtomicLong val = jvmInstanceGaugeMap.get(key);
if (null != val) {
val.set(instances);
return;
}
try {
val = new AtomicLong(instances);
meterRegistry.gauge(uk, Arrays.asList(Tag.of("name", "jvm_usage"), Tag.of("type", "instances"),Tag.of("class_name",key)),val,AtomicLong::get);
jvmInstanceGaugeMap.put(key, val);
} catch (Exception e) {
log.error("metrics jvmInstanceGaugeMap error,key:{},method:{},time:{}", uk, val, e);
}
}
[I、[J... 会在后续文章中给出解释
一些类注入Prometheus后,如果程序逻辑跑完会被JVM回收,为尽量确保开发同学每次打开大盘时看到的数据有参考意义,需要在注入后再补一个清除逻辑:
public void clearHistoryKey(Collection<String> keys,String key){
Collection<Meter> meters = meterRegistry.get(key).meters();
for (Meter meter : meters) {
Meter.Id id = meter.getId();
String className = id.getTag("class_name");
if (!StringUtils.isBlank(className) && !keys.contains(className)){
meterRegistry.remove(meter);
jvmInstanceGaugeMap.remove(className);
jvmByteGaugeMap.remove(className);
log.debug("clearHistoryKey key:{} clzName:{}",key,className);
}
}
}
效果展示
需要说明的是:相关指标注入到Prometheus后与运维同学沟通,对方会访问每个服务对外提供的actuator/promethues接口,将拿到的数据解析后再与其它工具集成展示 (目前我们是Grafana),开发同学关注时只需要登录上去瞄一眼就行。从此相看,两生欢喜。
动态配置
Promethues多久采集一次数据(默认 15s)这些是可以通过配置修改的,有兴趣的可以自行
试试,这里面:https://prometheus.io/docs/prometheus/latest/getting_started/ 有详细的说明。
多数情况下配置文件变更后,需要更新配置到程序内存里,有两种方式,第一种简单粗暴,就是重启第二种是动态更新的方式(nginx也有)。如何实现动态的更新Prometheus配置,步骤如下:
step1.启动Prometheus的时候带上启动参数:--web.enable-lifecycle
prometheus.exe --config.file=/xxx/prometheus.yml --web.enable-lifecycle
step2.更新Prometheus配置
step3.更新完配置后,通过Post请求的方式,动态更新配置:
curl -v --request POST 'http://localhost:9090/-/reload'
P.S:自定义指标部分http耗时和JVM字节码都暂时只列出了部分代码块,考虑到二者是完全独立的功能,所以业务调用侧的代码会分别在后续篇章再给出。
划水专用:
https://prometheus.io/
https://zhuanlan.zhihu.com/p/488898953?utm_id=0
https://blog.csdn.net/gaowenhui2008/article/details/131598092
https://blog.csdn.net/jilo88/article/details/131424717
有问题欢迎指出,听说吐槽与关注更配哦
书山有路勤为径,学海无涯苦作舟。
——韩愈《劝学》