http://colobu.com/2014/08/08/Metrics-and-Spring-Integration/
Metrics可以为你的代码的运行提供无与伦比的洞察力。作为一款监控指标的度量类库,它提供了很多模块可以为第三方库或者应用提供辅助统计信息, 比如Jetty, Logback, Log4j, Apache HttpClient, Ehcache, JDBI, Jersey, 它还可以将度量数据发送给Ganglia和Graphite以提供图形化的监控。
Metrics提供了Gauge、Counter、Meter、Histogram、Timer等度量工具类以及Health Check功能。
Metrics Graphite dashboard
将metrics-core加入到maven pom.xml中:
1
2
3
4
5
6
7
|
<dependencies>
<dependency>
<groupId>com.codahale.metrics
groupId>
<artifactId>metrics-core
artifactId>
<version>${metrics.version}
version>
dependency>
dependencies>
|
将metrics.version
设置为metrics最新的版本。
现在你可以在你的程序代码中加入一些度量了。
Metric的中心部件是MetricRegistry
。 它是程序中所有度量metric的容器。让我们接着在代码中加入一行:
final MetricRegistry metrics = new MetricRegistry();
Gauge
代表一个度量的即时值。 当你开汽车的时候, 当前速度是Gauge值。 你测体温的时候, 体温计的刻度是一个Gauge值。 当你的程序运行的时候, 内存使用量和CPU占用率都可以通过Gauge值来度量。
比如我们可以查看一个队列当前的size。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class QueueManager {
private
final Queue queue;
public
QueueManager(MetricRegistry metrics, String name) {
this.queue =
new Queue();
metrics.register(MetricRegistry.name(QueueManager.class, name,
"size"),
new Gauge
@Override
public Integer
getValue() {
return queue.size();
}
});
}
}
|
registry
中每一个metric
都有唯一的名字。 它可以是以.连接的字符串。 如”things.count” 和 “com.colobu.Thing.latency”。 MetricRegistry
提供了一个静态的辅助方法用来生成这个名字:
1
|
MetricRegistry.name(QueueManager.class,
"jobs",
"size")
|
生成的name为com.colobu.QueueManager.jobs.size
。
实际编程中对于队列或者类似队列的数据结构,你不会简单的度量
queue.size()
, 因为在java.util和java.util.concurrent包中大部分的queue的#size是O(n),这意味的调用此方法会有性能的问题, 更深一步,可能会有lock的问题。
RatioGauge可以计算两个Gauge的比值。 Meter和Timer可以参考下面的代码创建。下面的代码用来计算计算命中率 (hit/call)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class CacheHitRatio extends RatioGauge {
private
final Meter hits;
private
final Timer calls;
public
CacheHitRatio(Meter hits, Timer calls) {
this.hits = hits;
this.calls = calls;
}
@Override
public Ratio
getValue() {
return Ratio.of(hits.oneMinuteRate(),
calls.oneMinuteRate());
}
}
|
CachedGauge可以缓存耗时的测量。DerivativeGauge可以引用另外一个Gauage。
Counter
是一个AtomicLong
实例, 可以增加或者减少值。 例如,可以用它来计数队列中加入的Job的总数。
1
2
3
4
5
6
7
8
9
10
11
|
private
final Counter pendingJobs = metrics.counter(name(QueueManager.class,
"pending-jobs"));
public
void
addJob(Job job) {
pendingJobs.inc();
queue.offer(job);
}
public Job
takeJob() {
pendingJobs.dec();
return queue.take();
}
|
和上面Gauage不同,这里我们使用的是metrics.counter方法而不是metrics.register方法。 使用metrics.counter更简单。
Meter
用来计算事件的速率。 例如 request per second。 还可以提供1分钟,5分钟,15分钟不断更新的平均速率。
1
2
3
4
5
6
|
private
final Meter requests = metrics.meter(name(RequestHandler.class,
"requests"));
public
void
handleRequest(Request request, Response response) {
requests.mark();
// etc
}
|
Histogram
可以为数据流提供统计数据。 除了最大值,最小值,平均值外,它还可以测量 中值(median),百分比比如XX%这样的Quantile数据 。
1
2
3
4
5
6
|
private
final Histogram responseSizes = metrics.histogram(name(RequestHandler.class,
"response-sizes");
public
void
handleRequest(Request request, Response response) {
// etc
responseSizes.update(response.getContent().length);
}
|
这个例子用来统计response的字节数。
Metrics提供了一批的Reservoir实现,非常有用。例如SlidingTimeWindowReservoir 用来统计最新N个秒(或其它时间单元)的数据。
Timer
用来测量一段代码被调用的速率和用时。
1
2
3
4
5
6
7
8
9
10
11
|
private
final Timer responses = metrics.timer(name(RequestHandler.class,
"responses"));
public String
handleRequest(Request request, Response response) {
final Timer.Context context = responses.time();
try {
// etc;
return
"OK";
}
finally {
context.stop();
}
}
|
这段代码用来计算中间的代码用时以及request的速率。
Metric
还提供了服务健康检查能力, 由metrics-healthchecks
模块提供。
先创建一个HealthCheckRegistry
实例。
1
|
final HealthCheckRegistry healthChecks =
new HealthCheckRegistry();
|
再实现一个HealthCheck
子类, 用来检查数据库的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class DatabaseHealthCheck extends HealthCheck {
private
final Database database;
public
DatabaseHealthCheck(Database database) {
this.database = database;
}
@Override
public HealthCheck.Result
check()
throws Exception {
if (database.isConnected()) {
return HealthCheck.Result.healthy();
}
else {
return HealthCheck.Result.unhealthy(
"Cannot connect to " + database.getUrl());
}
}
}
|
注册一下。
1
|
healthChecks.register(
"mysql",
new DatabaseHealthCheck(database));
|
最后运行健康检查并查看检查结果。
1
2
3
4
5
6
7
8
9
10
11
12
|
final Map
for (Entry
if (entry.getValue().isHealthy()) {
System.out.println(entry.getKey() +
" is healthy");
}
else {
System.err.println(entry.getKey() +
" is UNHEALTHY: " + entry.getValue().getMessage());
final Throwable e = entry.getValue().getError();
if (e !=
null) {
e.printStackTrace();
}
}
}
|
Metric
内置一个ThreadDeadlockHealthCheck, 它使用java内置的线程死锁检查方法来检查程序中是否有死锁。
通过JMX报告Metric。
1
2
|
final JmxReporter reporter = JmxReporter.forRegistry(registry).build();
reporter.start();
|
一旦启动, 所有registry中注册的metric都可以通过JConsole或者VisualVM查看 (通过MBean插件)。
通过JMX查看
Metric也提供了一个servlet (AdminServlet)提供JSON风格的报表。它还提供了单一功能的servlet (MetricsServlet, HealthCheckServlet, ThreadDumpServlet, PingServlet)。
你需要在pom.xml加入metrics-servlets。
1
2
3
4
5
|
<dependency>
<groupId>com.codahale.metrics
groupId>
<artifactId>metrics-servlets
artifactId>
<version>${metrics.version}
version>
dependency>
|
除了JMX和HTTP, metric还提供其它报表。
1
2
3
4
5
|
final ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(
1, TimeUnit.MINUTES);
|
1
2
3
4
5
6
|
final CsvReporter reporter = CsvReporter.forRegistry(registry)
.formatFor(Locale.US)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(
new File(
"~/projects/data/"));
reporter.start(
1, TimeUnit.SECONDS);
|
1
2
3
4
5
6
|
final Slf4jReporter reporter = Slf4jReporter.forRegistry(registry)
.outputTo(LoggerFactory.getLogger(
"com.example.metrics"))
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(
1, TimeUnit.MINUTES);
|
1
2
3
4
5
6
|
final GMetric ganglia =
new GMetric(
"ganglia.example.com",
8649, UDPAddressingMode.MULTICAST,
1);
final GangliaReporter reporter = GangliaReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(ganglia);
reporter.start(
1, TimeUnit.MINUTES);
|
可以将一组Metric组织成一组便于重用。
1
2
3
4
5
6
7
8
|
final Graphite graphite =
new Graphite(
new InetSocketAddress(
"graphite.example.com",
2003));
final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
.prefixedWith(
"web1.example.com")
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.filter(MetricFilter.ALL)
.build(graphite);
reporter.start(
1, TimeUnit.MINUTES);
|
这里重点介绍一下Metrics for Spring
这个库为Spring增加了Metric库, 提供基于XML或者注解方式。
你需要在pom.xml加入
1
2
3
4
5
|
<dependency>
<groupId>com.ryantenney.metrics
groupId>
<artifactId>metrics-spring
artifactId>
<version>3.0.1
version>
dependency>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:metrics=
"http://www.ryantenney.com/schema/metrics"
xsi:schemaLocation=
"
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.ryantenney.com/schema/metrics
http://www.ryantenney.com/schema/metrics/metrics-3.0.xsd">
<metrics:metric-registry id="metrics" />
<metrics:annotation-driven metric-registry="metrics" />
<metrics:reporter type="console" metric-registry="metrics" period="1m" />
<metrics:register metric-registry="metrics">
<bean metrics:name="jvm.gc" class="com.codahale.metrics.jvm.GarbageCollectorMetricSet" />
<bean metrics:name="jvm.memory" class="com.codahale.metrics.jvm.MemoryUsageGaugeSet" />
<bean metrics:name="jvm.thread-states" class="com.codahale.metrics.jvm.ThreadStatesGaugeSet" />
<bean metrics:name="jvm.fd.usage" class="com.codahale.metrics.jvm.FileDescriptorRatioGauge" />
metrics:register>
beans>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
@Configuration
@EnableMetrics
public
class SpringConfiguringClass extends MetricsConfigurerAdapter {
@Override
public
void
configureReporters(MetricRegistry metricRegistry) {
ConsoleReporter
.forRegistry(metricRegistry)
.build()
.start(
1, TimeUnit.MINUTES);
}
}
|
更多的信息请参照这里