目录
Metrics
介绍
监控工具。Metrics提供了强大的工具用于监控重要组件的行为。Metrics依赖Jetty, Logback, Log4j, Apache HttpClient, Ehcache, JDBI, Jersey和Graphite,提供一个可视化程序执行情况。
Metrics可以统计TPS,方法执行次数,队列情况,方法执行时间等,可以支持生成仪表盘格式,柱状图格式的数据。并且支持将此类数据输出到控制台、日志文件、CSV格式文件中。也可以通过简单配置提供返回JMX格式的接口。
使用
Metric Registries
Metric注册中心是Metric工具的入口,用于提供创建各种监控工具和存储这些监控工具。通常情况下一个应用只需要一个Metrics注册中心,并且应用初始化时初始化注册中心。如果应用需要多个Metric注册中心,可以使用SharedMetricRegistries来管理。SharedMetricRegistries维护一个继承自ConcurrentMap的REGISTRIES对象,key是注册中心的名字,value是注册中心实例。SharedMetricRegistries提供一些基本的维护注册中心(添加和删除注册中心)方法。
如下是Spring Boot利用注册方式初始化一个Metric Registries:
@Configuration
public class Configuration {
@Bean
MetricRegistry metricRegistry() {
return new MetricRegistry();
}
}
Metric Registries利用ConcurrentMap来存储所有的监控,key是监控的名字,value是监控的实例。所以注册中心要求每个监控的名字必须唯一。MetricRegistry提供了生成监控名称的方法
/**
* Concatenates elements to form a dotted name, eliding any null values or empty strings.
*
* @param name the first element of the name
* @param names the remaining elements of the name
* @return {@code name} and {@code names} concatenated by periods
*/
public static String name(String name, String... names) {
final StringBuilder builder = new StringBuilder();
append(builder, name);
if (names != null) {
for (String s : names) {
append(builder, s);
}
}
return builder.toString();
}
官网推荐使用 类对象+业务对象+业务对象属性来命名监控名称,比如:
public static void main(String[] args) {
String name = MetricRegistry.name(Queue.class, "requests", "size");
}
Gauges
gauge是一种只监控统计一个唯独的监控数据类型,比如可以统计程序启动后接口被访问了多少次:
@org.springframework.context.annotation.Configuration
public class Configuration {
@Bean
MetricRegistry metricRegistry() {
return new MetricRegistry();
}
// 配置控制台输出监控指标
@Bean
ConsoleReporter consoleReporter() {
ConsoleReporter reporter = ConsoleReporter.forRegistry(metricRegistry())
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(5, TimeUnit.SECONDS);
return reporter;
}
}
@RestController
public class GaugesController {
// test方法被访问的次数
private int countTest = 0;
// register在项目启动过程中已经被Spring初始化并加入到bean容器中
public GaugesController(MetricRegistry registry) {
registry.register("test", (Gauge<Integer>) () -> countTest);
}
@RequestMapping(value = "/gauges/test")
public String gauges() {
countTest++;
return String.valueOf(countTest);
}
}
控制台每5秒钟输出结果,随着每次点击 test值不断增加。本次点击测试的输入如下:
#第一次输入(本人点击了一次)
-- Gauges ----------------------------------------------------------------------
test
value = 1
# 第二次的5秒输出(本人累计点击7次)
-- Gauges ----------------------------------------------------------------------
test
value = 7
这里只是举了一个使用gauge的例子,Metrics有单独提供用于统计次数的监控数据类型Counters。
gauges还有一种可以一个监控两组数据比率的监控数据类型RatioGauge,以下列举两个接口调用次数的比例来练习RatioGauge的使用。
@RestController
public class GaugesController {
public GaugesController(MetricRegistry registry) {
registry.register("test", (Gauge<Integer>) () -> countTest);
// 注册一个RatioGauge
RatioGauge ratioGauge = new RatioGauge() {
@Override
protected Ratio getRatio() {
// ratio返回 接口/gauges/ratio调用次数除以
return Ratio.of(countRatioGauges, countTest + countRatioGauges);
}
};
registry.register("test2", ratioGauge);
}
@GetMapping(value = "/gauges/test")
public String gauges() {
countTest++;
return String.valueOf(countTest);
}
// 增加一个接口,并统计调用次数
@GetMapping(value = "/gauges/ratio")
public String ratioGauges() {
countRatioGauges++;
return String.valueOf(countRatioGauges);
}
private int countTest = 0;
private int countRatioGauges = 0;
当/gauges/test请求访问两次,/gauges/ratio请求访问6次 test2指标的输出结果:
-- Gauges ----------------------------------------------------------------------
test2
value = 0.75
Gauges 提供一个能够缓存指定时间的监控CachedGauge,以下是CachedGauge的例子:
// 注册一个Cached Gauge
@Bean
CachedGauge<Long> cachedGauge(MetricRegistry registry) {
CachedGauge<Long> cachedGauge = new CachedGauge<Long>(10000, TimeUnit.MILLISECONDS) {
@Override
protected Long loadValue() {
try {
System.out.println("start CachedGauge before execute loadValue " + LocalDateTime.now());
// 获取具体的内容
long value = longTimeValue();
System.out.println("start CachedGauge after execute loadValue " + LocalDateTime.now());
return value;
} catch (InterruptedException e) {
System.out.println(e);
return 0L;
}
}
};
registry.register("cached", cachedGauge);
return cachedGauge;
}
/**
* 睡眠2秒后返回一个随机数
*/
private long longTimeValue() throws InterruptedException {
Thread.sleep(2000);
return random.nextInt(100);
}
private Random random = new Random();
/**
* 随机数生成器
*/
private Random random;
Cached Guage可以在第一次获取到结果后,将结果缓存。在缓存未失效前,如果需要获取结果则直接返回缓存中的结果;如果缓存失效则再次执行获取结果的任务,并缓存结果。
Gauges提供了一种可以自定义转换监控数据的监控类型DerivativeGauge,一下是DerivativeGauge使用例子:
// 定义DerivativeGauge
public GaugesController(MetricRegistry registry) {
// 定义一个Gauge,用于统计接口gauges/test的访问次数
Gauge<Long> gauge = () -> (long)countTest;
// 初始化DerivativeGauge,自定义统计接口访问次数的指标如何转成成其他指标
DerivativeGauge<Long, String> derivativeGauge = new DerivativeGauge<Long, String>(gauge) {
@Override
protected String transform(Long value) {
return String.format("这是访问第%s", value);
}
};
registry.register("derivative", derivativeGauge);
}
@GetMapping(value = "/gauges/test")
public String gauges() {
// 非线程安全,只作为一个演示例子
countTest++;
return String.valueOf(countTest);
}
// 记录接口访问次数
private int countTest = 0;
以下是控制台输出:
```
– Gauges -----------------------------------------------------------------–—
derivative
value = 这是访问第3
```
Counter
Counter是一个计算器监控类型,基于LongAdder实现的计数,以下是使用的例子:
@RestController
public class CounterController {
public CounterController(MetricRegistry registry) {
//初始化一个Counter类型的监控
counter = new Counter();
registry.register("counter", counter);
}
@GetMapping("counter/add")
public long add() {
// 每次方法调用时自增一
counter.inc();
return counter.getCount();
}
@GetMapping("counter/minus")
public long method1() {
// 方法调用时自减一
counter.dec();
return counter.getCount();
}
/**
* 计算器
*/
private Counter counter;
counter/add 请求调用5次,counter/minus请求调用12次后监控输出如下
-- Counters --------------------------------------------------------------------
counter
count = -7
Histograms
Histogram是用来统计一个监控指标在连续的时间纬度上的分布情况的直方图监控类型,监控的包括:次数,最小值,最大值,均值,标准差,中位数,75%、95%、98%、99%、99.9%分位值。Histogram只适用于小数据量下的计算。
Histogram在数据修改时,通过采样将数据维护在一个小的、可管理的、在统计上代表整个数据样本的存储库上,通过计算存储库上的数据得到统计结果。
使用例子如下:
@RestController
public class HistogramController {
public HistogramController(MetricRegistry registry) {
longAdder = new LongAdder();
histogram = registry.histogram(name(HistogramController.class, "counts"));
}
@GetMapping("histogram")
public long histogram() {
longAdder.increment();
long value = longAdder.longValue();
histogram.update(value);
return value;
}
private LongAdder longAdder;
private Histogram histogram;
}
Histogram常用于监控接口响应时间,缓存大小等场景。
Meters
Meters用于监控事件发生的频率,比如每秒接口被调用的次数。一下是Meter的使用例子:
public MeterController(MetricRegistry registry) {
getRequests = registry.meter(name(MeterController.class, "meter-requests", "requests"));
}
@GetMapping("/meter/1")
public String meter() {
getRequests.mark();
return null;
}
@GetMapping("/meter/2")
public String meter2() {
getRequests.mark();
return null;
}
@GetMapping("/meter/3")
public String meter3() {
getRequests.mark();
return null;
}
快速访问接口/meter/1,日志输出如下:
-- Meters ----------------------------------------------------------------------
com.jackframe.practice.metrics.meter.MeterController.meter-requests.requests
count = 17
mean rate = 1.70 events/second
1-minute rate = 1.28 events/second
5-minute rate = 1.22 events/second
15-minute rate = 1.21 events/second
Meters监控的是事件的触发次数,每秒钟触发的中位数,1分钟内/5分钟内/15分钟内每秒触发的次数,经常用于监控接口访问情况
Timers
Timers是Meters和Histogram的组合,使用如下:
public TimerController(MetricRegistry registry) {
timer = registry.timer(name(TimerController.class, "get-requests"));
}
@GetMapping("timer")
public String timer() {
final Timer.Context context = timer.time();
try {
System.out.println("execute task");
} finally {
context.stop();
}
return "";
}
private final Timer timer;
快速访问/timer接口17次后,监控输入如下:
-- Timers ----------------------------------------------------------------------
com.jackframe.practice.metrics.timer.TimerController.get-requests
count = 17
mean rate = 1.70 calls/second
1-minute rate = 0.61 calls/second
5-minute rate = 0.44 calls/second
15-minute rate = 0.41 calls/second
min = 0.03 milliseconds
max = 0.15 milliseconds
mean = 0.04 milliseconds
stddev = 0.03 milliseconds
median = 0.03 milliseconds
75% <= 0.03 milliseconds
95% <= 0.15 milliseconds
98% <= 0.15 milliseconds
99% <= 0.15 milliseconds
99.9% <= 0.15 milliseconds
监控输出方式
Metrics监控结果输出方式支持输出控制台,SLF4J(日志文件),CSV(文件)。
以下是定一个每10秒一次将监控数据输出到控制台的输出配置:
// 注册一个控制台输出工具
ConsoleReporter reporter = ConsoleReporter.forRegistry(metricRegistry())
.convertRatesTo(TimeUnit.SECONDS) // 输出比率的时间单位
.convertDurationsTo(TimeUnit.MILLISECONDS) // 输出频率的时间单位
.build();
reporter.start(10, TimeUnit.SECONDS); // 定义每10秒执行一次输出
return reporter;
生产环境上的此类信息一般会入日志系统,Metrics提供将监控输出到日志的方式。一下时输出到日志的配置:
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);