使用Java、prometheus 自定义nginxP99指标,用grafana展示数据
·
运行代码
1.nginx.conf 首先加入获取请求耗时的模块
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time';
access_log /home/nginxWebUI/log/access.log main;
2.网页打开http://localhost:9099/actuator/prometheus
3.可以看到 nginx_request_latency_histogram_seconds_bucket{application="nginx-monitor",le="0.001",} 0.0
4.然后再prometheus.yaml 中配置
- job_name: 'nginx-monitor'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['10.211.55.17:9099']
scrape_interval: 5s
5.然后再grafana 中配置p99指标即可
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))
想获取nginx请求耗时的p99指标:nginx_request_latency_histogram_seconds_bucket
1.nginx-prometheus-exporter和nginx-vts 两种是无法暴露这个指标的
2.openresty 需要自定的lua脚本、各种的配置和nginx-plus 都会改变原先的nginx结构
在不改变原配置的情况下 使用java+prometheus 自定义nginx的p99指标且暴露接口,并用grafana展示p99耗时数据。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
package com.NinxMonitor.Collector;
import com.NinxMonitor.pojo.NginxLogEntry;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.*;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Component
public class NginxMetricsCollector {
private final MeterRegistry meterRegistry;
private final DistributionSummary requestLatency;
private final Map<Integer, Timer> statusCodeTimers = new ConcurrentHashMap<>();
private long lastPosition = 0;
//private static final String LOG_PATH = "/Users/zero/Desktop/access.log";
//private static final String LOG_PATH = "${log.path}";
private final Timer requestLatencyHistogram;
@Value("${log.path}")
String LOG_PATH;
private static final Pattern LOG_PATTERN = Pattern.compile(
"^(\\S+) - \\S+ \\[(.*?)\\] \"(.*?)\" (\\d+) (\\d+) \"(.*?)\" \"(.*?)\" \"(.*?)\" ([0-9.]+)$"
);
/**
* 将指标注册到普罗米修斯
* @param meterRegistry
* DistributionSummary 计算p指标的值 DistributionSummary 会将记录的延迟数据存储在分桶(Buckets)中,分桶的粒度由 Micrometer 内部配置决定。
*/
public NginxMetricsCollector(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 请求延迟指标
this.requestLatency = DistributionSummary.builder("nginx_request_latency_seconds")
.description("Nginx request latency in seconds")
.publishPercentiles(0.50, 0.90, 0.95, 0.99)
.scale(1)
.register(meterRegistry);
this.requestLatencyHistogram = Timer.builder("nginx_request_latency_histogram")
.description("Nginx request latency in seconds (histogram)")
.publishPercentileHistogram()
.serviceLevelObjectives(
Duration.ofMillis(10), // 10ms
Duration.ofMillis(50), // 50ms
Duration.ofMillis(100), // 100ms
Duration.ofMillis(200), // 200ms
Duration.ofMillis(500), // 500ms
Duration.ofSeconds(1), // 1s
Duration.ofSeconds(2), // 2s
Duration.ofSeconds(5) // 5s
).register(meterRegistry);
}
/**
* 将两种指标的record方式合一 记录延迟指标
* @param seconds
*/
public void recordLatency(double seconds) {
requestLatency.record(seconds);
requestLatencyHistogram.record(Duration.ofNanos((long)(seconds * 1_000_000_000))); //纳秒数的 Duration 对象
}
/**
* 定时任务执行获取日志信息
*/
@Scheduled(fixedRate = 5000) //五秒执行一次
public void collectMetrics() {
log.info("加载时间"+new Date(System.currentTimeMillis()));
try {
Path logPath = Paths.get(LOG_PATH);
log.info("日志加载路径:"+LOG_PATH);
if (!Files.exists(logPath)) {
log.warn("Nginx log file not found: {}", LOG_PATH);
return;
}
long fileSize = Files.size(logPath);
if (fileSize < lastPosition) {
// 日志文件被轮转,重置位置
lastPosition = 0;
}
if (fileSize > lastPosition) {
try (RandomAccessFile file = new RandomAccessFile(LOG_PATH, "r")) {
file.seek(lastPosition);
String line;
while ((line = file.readLine()) != null) {
processLogLine(line); //读取一行计算指标
}
lastPosition = file.getFilePointer();
}
}
} catch (IOException e) {
log.error("Error reading nginx log file", e);
}
}
/**
* 记录请求耗时
* @param line
*/
private void processLogLine(String line) {
try {
NginxLogEntry entry = parseLogLine(line); //parseLogLine 正则表达式将值写入对象
//log.info("获取的值对象:"+entry);
if (entry != null) {
// 记录请求延迟
//requestLatency.record(entry.getRequestTime()); //直接计算指标
recordLatency(entry.getRequestTime()); //两种记录指标 record 方法时,延迟数据添加到分布统计中
// 按状态码记录请求
Timer statusTimer = statusCodeTimers.computeIfAbsent(
entry.getStatus(),
status -> Timer.builder("nginx_request_status")
.tag("status", String.valueOf(status))
.register(meterRegistry)
);
statusTimer.record(java.time.Duration.ofMillis((long)(entry.getRequestTime() * 1000)));
}
} catch (Exception e) {
log.error("Error processing log line: {}", line, e);
}
}
private NginxLogEntry parseLogLine(String line) {
Matcher matcher = LOG_PATTERN.matcher(line);
if (!matcher.matches()) {
return null;
}
NginxLogEntry entry = new NginxLogEntry();
entry.setRemoteAddr(matcher.group(1));
entry.setTimeLocal(matcher.group(2));
entry.setRequest(matcher.group(3));
entry.setStatus(Integer.parseInt(matcher.group(4)));
entry.setBodyBytesSent(Long.parseLong(matcher.group(5)));
entry.setHttpReferer(matcher.group(6));
entry.setHttpUserAgent(matcher.group(7));
entry.setHttpXForwardedFor(matcher.group(8));
entry.setRequestTime(Double.parseDouble(matcher.group(9)));
return entry;
}
}
package com.NinxMonitor.conf;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class MetricsConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
package com.NinxMonitor.pojo;
import lombok.Data;
@Data
public class NginxLogEntry {
private String remoteAddr;
private String timeLocal;
private String request;
private int status;
private long bodyBytesSent;
private String httpReferer;
private String httpUserAgent;
private String httpXForwardedFor;
private double requestTime;
}
//启动类上加入开启定时任务注解
@EnableScheduling
server:
port: 9099
spring:
application:
name: nginx-monitor
management:
endpoints:
web:
exposure:
include: prometheus,health,info,metrics
base-path: /actuator
endpoint:
health:
show-details: always
prometheus:
enabled: true
metrics:
tags:
application: ${spring.application.name} #为所有指标添加应用名称标签
distribution:
percentiles-histogram:
http.server.requests: true # 启用 HTTP 请求的直方图统计
sla:
http.server.requests: 1ms,10ms,100ms,1s,10s
#percentiles:
# http.server.requests: 0.5,0.95,0.99 # 额外添加百分位数计算
export:
prometheus:
enabled: true
log:
path: /Users/zero/Desktop/access.log更多推荐


所有评论(0)