1. 简介
Metrics 是一个为 Java 应用提供测量工具的库。它包含多个模块,本文将重点介绍以下核心模块:
metrics-core
:核心指标功能metrics-healthchecks
:健康检查metrics-servlets
:Servlet 集成metrics-servlet
:请求过滤指标
其他模块也会简要概述,供参考。
2. 模块 metrics-core
2.1 Maven 依赖
只需在 pom.xml
中添加一个依赖:
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>4.2.17</version>
</dependency>
最新版本可在 Maven 仓库 查询。
2.2 MetricRegistry
简单说,MetricRegistry
用于注册一个或多个指标。✅ 推荐用法:
- 全局使用单一注册表
- 若需不同报告方式,可分组使用多个注册表
创建注册表:
MetricRegistry metricRegistry = new MetricRegistry();
注册指标有两种方式:
// 方式1:手动创建实例
Meter meter1 = new Meter();
metricRegistry.register("meter1", meter1);
// 方式2:通过注册表创建(推荐)
Meter meter2 = metricRegistry.meter("meter2");
⚠️ 每个指标必须有唯一名称。注册表提供静态方法辅助命名:
String name1 = MetricRegistry.name(Filter.class, "request", "count");
String name2 = MetricRegistry.name("CustomFilter", "response", "count");
管理多个注册表时,使用线程安全的单例 SharedMetricRegistries
:
SharedMetricRegistries.add("default", metricRegistry);
MetricRegistry retrievedRegistry = SharedMetricRegistries.getOrCreate("default");
SharedMetricRegistries.remove("default");
3. 核心指标类型
metrics-core
提供六种核心指标类型:
3.1 Meter
事件速率测量
测量事件发生次数和速率:
Meter meter = new Meter();
long initCount = meter.getCount(); // 初始为0
assertThat(initCount, equalTo(0L));
meter.mark(); // 标记1次事件
assertThat(meter.getCount(), equalTo(1L));
meter.mark(20); // 标记20次事件
assertThat(meter.getCount(), equalTo(21L));
// 获取不同时间窗口的平均速率
double meanRate = meter.getMeanRate(); // 全生命周期平均速率
double oneMinRate = meter.getOneMinuteRate(); // 最近1分钟平均速率
double fiveMinRate = meter.getFiveMinuteRate(); // 最近5分钟平均速率
double fifteenMinRate = meter.getFifteenMinuteRate(); // 最近15分钟平均速率
3.2 Gauge
即时值获取
Gauge
是一个接口,用于返回特定值。核心实现包括:
RatioGauge
:比率计算CachedGauge
:带缓存的值DerivativeGauge
:派生值JmxAttributeGauge
:JMX 属性访问
比率计算示例:
public class AttendanceRatioGauge extends RatioGauge {
private int attendanceCount;
private int courseCount;
@Override
protected Ratio getRatio() {
return Ratio.of(attendanceCount, courseCount);
}
// 构造方法省略...
}
// 测试
RatioGauge ratioGauge = new AttendanceRatioGauge(15, 20);
assertThat(ratioGauge.getValue(), equalTo(0.75)); // 15/20=0.75
缓存值示例(适合计算开销大的场景):
public class ActiveUsersGauge extends CachedGauge<List<Long>> {
@Override
protected List<Long> loadValue() {
return getActiveUserCount(); // 模拟耗时操作
}
private List<Long> getActiveUserCount() {
return Arrays.asList(12L); // 模拟返回值
}
// 构造方法省略...
}
// 测试(缓存15分钟)
Gauge<List<Long>> gauge = new ActiveUsersGauge(15, TimeUnit.MINUTES);
assertThat(gauge.getValue(), equalTo(Arrays.asList(12L)));
3.3 Counter
计数器
记录增减操作:
Counter counter = new Counter();
counter.inc(); // +1
counter.inc(11); // +11
counter.dec(); // -1
counter.dec(6); // -6
assertThat(counter.getCount(), equalTo(5L)); // 1+11-1-6=5
3.4 Histogram
直方图
跟踪 Long
值流并计算统计特征(最大值、最小值、均值、百分位等):
Histogram histogram = new Histogram(new UniformReservoir());
histogram.update(5);
histogram.update(20);
Snapshot snapshot = histogram.getSnapshot();
assertThat(snapshot.getMax(), equalTo(20L)); // 最大值
assertThat(snapshot.getMean(), equalTo(12.5)); // 均值
assertThat(snapshot.get75thPercentile(), equalTo(20.0)); // 75百分位
assertThat(snapshot.getStdDev(), closeTo(10.6, 0.1)); // 标准差
⚠️ 需显式指定采样器(Reservoir),常用实现:
ExponentiallyDecayingReservoir
:指数衰减采样(默认)UniformReservoir
:均匀采样SlidingTimeWindowReservoir
:滑动时间窗口SlidingWindowReservoir
:滑动窗口
3.5 Timer
计时器
跟踪多个时间段的统计信息:
Timer timer = new Timer();
Timer.Context context = timer.time();
TimeUnit.SECONDS.sleep(5); // 模拟耗时操作
long elapsedNanos = context.stop(); // 纳秒精度
assertThat(timer.getCount(), equalTo(1L));
assertThat(timer.getMeanRate(), closeTo(0.2, 0.1)); // 平均速率
3.6 Reporter
指标报告器
输出指标数据的接口,核心实现:
ConsoleReporter
:控制台输出CsvReporter
:CSV 文件输出Slf4jReporter
:日志输出JmxReporter
:JMX 输出
控制台输出示例:
MetricRegistry registry = new MetricRegistry();
registry.meter("meter").mark(200);
registry.histogram("histogram").update(12);
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(5, TimeUnit.SECONDS); // 每5秒输出一次
输出示例:
-- Histograms ------------------------------------------------------------------
histogram
count = 1
min = 12
max = 12
mean = 12.00
stddev = 0.00
median = 12.00
75% <= 12.00
95% <= 12.00
98% <= 12.00
99% <= 12.00
99.9% <= 12.00
-- Meters ----------------------------------------------------------------------
meter
count = 200
mean rate = 40.00 events/second
1-minute rate = 0.00 events/second
5-minute rate = 0.00 events/second
15-minute rate = 0.00 events/second
4. 模块 metrics-healthchecks
4.1 Maven 依赖
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
<version>4.2.17</version>
</dependency>
4.2 使用方式
步骤1:实现健康检查逻辑(继承 HealthCheck
):
public class DatabaseHealthCheck extends HealthCheck {
@Override
protected Result check() throws Exception {
// 实际检查逻辑(如数据库连接测试)
return Result.healthy(); // 或 Result.unhealthy("错误原因")
}
}
步骤2:注册到 HealthCheckRegistry
:
HealthCheckRegistry registry = new HealthCheckRegistry();
registry.register("db", new DatabaseHealthCheck());
registry.register("uc", new UserCenterHealthCheck());
// 执行所有检查
Map<String, HealthCheck.Result> results = registry.runHealthChecks();
results.forEach((name, result) ->
assertThat(result.isHealthy(), equalTo(true))
);
// 执行单个检查
HealthCheck.Result dbResult = registry.runHealthCheck("db");
5. 模块 metrics-servlets
5.1 Maven 依赖
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-servlets</artifactId>
<version>4.2.17</version>
</dependency>
5.2 HealthCheckServlet
健康检查接口
步骤1:暴露 HealthCheckRegistry
:
public class MyHealthCheckContextListener extends HealthCheckServlet.ContextListener {
public static HealthCheckRegistry REGISTRY = new HealthCheckRegistry();
static {
REGISTRY.register("db", new DatabaseHealthCheck());
}
@Override
protected HealthCheckRegistry getHealthCheckRegistry() {
return REGISTRY;
}
}
步骤2:配置 web.xml
:
<listener>
<listener-class>com.example.MyHealthCheckContextListener</listener-class>
</listener>
<servlet>
<servlet-name>healthCheck</servlet-name>
<servlet-class>com.codahale.metrics.servlets.HealthCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>healthCheck</servlet-name>
<url-pattern>/healthcheck</url-pattern>
</servlet-mapping>
访问 GET /healthcheck
返回:
{
"db": {
"healthy": true
}
}
5.3 ThreadDumpServlet
线程转储
直接配置 web.xml
:
<servlet>
<servlet-name>threadDump</servlet-name>
<servlet-class>com.codahale.metrics.servlets.ThreadDumpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>threadDump</servlet-name>
<url-pattern>/threaddump</url-pattern>
</servlet-mapping>
访问 GET /threaddump
获取 JVM 线程快照。
5.4 PingServlet
存活检测
配置 web.xml
:
<servlet>
<servlet-name>ping</servlet-name>
<servlet-class>com.codahale.metrics.servlets.PingServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ping</servlet-name>
<url-pattern>/ping</url-pattern>
</servlet-mapping>
访问 GET /ping
返回 pong
(HTTP 200)。
5.5 MetricsServlet
指标接口
步骤1:暴露 MetricRegistry
:
public class MyMetricsContextListener extends MetricsServlet.ContextListener {
private static MetricRegistry REGISTRY = new MetricRegistry();
static {
REGISTRY.counter("m01-counter").inc();
REGISTRY.histogram("m02-histogram").update(5);
}
@Override
protected MetricRegistry getMetricRegistry() {
return REGISTRY;
}
}
步骤2:配置 web.xml
:
<listener>
<listener-class>com.example.MyMetricsContextListener</listener-class>
</listener>
<servlet>
<servlet-name>metrics</servlet-name>
<servlet-class>com.codahale.metrics.servlets.MetricsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>metrics</servlet-name>
<url-pattern>/metrics</url-pattern>
</servlet-mapping>
访问 GET /metrics
返回:
{
"version": "3.0.0",
"counters": {
"m01-counter": { "count": 1 }
},
"histograms": {
"m02-histogram": {
"count": 1,
"max": 5,
"mean": 5.0,
"min": 5,
"p50": 5,
"p75": 5,
"p95": 5,
"p98": 5,
"p99": 5,
"p999": 5,
"stddev": 0.0
}
}
}
5.6 AdminServlet
管理聚合接口
聚合上述所有 Servlet,配置 web.xml
:
<servlet>
<servlet-name>admin</servlet-name>
<servlet-class>com.codahale.metrics.servlets.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>admin</servlet-name>
<url-pattern>/admin/*</url-pattern>
</servlet-mapping>
访问 /admin
显示包含以下链接的页面:
/admin/healthcheck
/admin/metrics
/admin/ping
/admin/threaddump
6. 模块 metrics-servlet
6.1 Maven 依赖
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-servlet</artifactId>
<version>4.2.17</version>
</dependency>
6.2 使用方式
步骤1:暴露 MetricRegistry
:
public class MyFilterContextListener extends InstrumentedFilterContextListener {
public static MetricRegistry REGISTRY = new MetricRegistry();
@Override
protected MetricRegistry getMetricRegistry() {
return REGISTRY;
}
}
步骤2:配置 web.xml
:
<listener>
<listener-class>com.example.MyFilterContextListener</listener-class>
</listener>
<filter>
<filter-name>instrumentFilter</filter-name>
<filter-class>com.codahale.metrics.servlet.InstrumentedFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>instrumentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
自动收集以下指标:
- HTTP 状态码计数器(如
200
,404
) - 活跃请求数计数器
- 请求耗时计时器
7. 其他扩展模块
模块 | 功能 |
---|---|
metrics-jvm |
JVM 内部指标(内存、GC、线程等) |
metrics-ehcache |
Ehcache 缓存指标 |
metrics-httpclient |
Apache HttpClient 4.x 指标 |
metrics-log4j /metrics-log4j2 |
Log4j 日志级别速率统计 |
metrics-logback |
Logback 日志级别速率统计 |
metrics-json |
Jackson 集成(HealthCheckModule/MetricsModule) |
此外,第三方库 提供了与其他框架的集成支持。
8. 总结
应用监控是生产环境的刚需,Dropwizard Metrics 提供了简单粗暴的解决方案。本文覆盖了核心模块的使用方式,完整示例代码可在 GitHub 获取。踩坑提示:
- ❌ 避免在循环中频繁创建指标
- ✅ 合理使用采样器控制内存开销
- ⚠️ 生产环境慎用
ConsoleReporter