深入理解 Metrics(一):Gauges

  • Post author:
  • Post category:其他


1. 介绍

Metrics 是 dropwizard.io 开源的一款 Java 工具包,为开发者提供了多种计算指标的工具类,用于单个 JVM 进程指标监控。通过适配器方式支持 Jetty、Logback、Log4j、Apache HttpClient 和 graphite 等开源库的指标监控。

Metrics 提供监控的工具:

  • Gauges:指标当前值。

  • Counters:指标自增自减。

  • Histograms: 指标分布情况,最大、最小、TP99 等。

  • Meters:指标频率,例如 TPS。

  • Timers:Histograms 和 Meters 结合使用。

  • Health Checks:服务健康状况监控。


指标结果输出方式:

  • ConsoleReporter:控制台

  • CsvReporter:CSV 文件

  • Slf4jReporter:Logback、Log4j 等日志输出

  • JmxReporter:基于 JMX 的输出

  • GangliaReporter:监控工具 Ganglia

  • GraphiteReporter:监控工具 Graphite

2. 实际应用

account 是 SDMK 的核心模块,提供用户管理、角色管理、权限管理、用户授权和用户鉴权功能,在 account 内部使用 Metrics 作为服务度量的工具。


Metrics 应用的具体场景:

  • Meters:统计鉴权接口的吞吐量。

  • Gauges:定时输出缓存键的数量。

  • Counters:统计鉴权接口失败的次数。

  • Histograms: 统计鉴权接口的最大、最小、平均的响应时间。

  • Timers:统计鉴权接口响应时间的分布,同时输出吞吐量信息。

  • Health Checks:account 模块是否存活。

3. Gauges 的使用


添加 maven 依赖:

<dependencies>  <dependency>    <groupId>io.dropwizard.metrics</groupId>    <artifactId>metrics-core</artifactId>    <version>3.2.3</version>  </dependency></dependencies>

复制代码

为了使输出的结果更加直观,使用 ConsoleReporter,代码如下:

public class MetricsGaugesTest extends Test0Abstract {  private HashMap<String, String> appMap;  private ConcurrentMap<String, String> tokenMap;  @Autowired  private ApiLoginService apiLoginService;
  @Before  public void init() {    appMap = new HashMap<>(8);    appMap.put("b8a424e934264e769d450647b6ba62ca","fe72444d2ee94077b6949b647bdd3a14");    appMap.put("998bf8bd21c54571bdedef4d4d49cb87", "4e62603c33b84761a9289062edadc526");    appMap.put("ab19e506da564fb7a82a4bd2c8d237bd","c085ca4ade374f04aedc0f6b16422d0a");    appMap.put("073d546d30444d4eaed8344c7f42c782","df91daba26164123a7c595518ac6e517");    appMap.put("779f6078eace46ca9a21c8bee9bdb932","ec2a1d29087f49448f9507ef7b728ebf");    appMap.put("cda9f6e06a04440e8214c09ba171e991","dac3bbf93ed146238a9b3d68a7357e11");    tokenMap = new ConcurrentHashMap<>();  }
  @Test  public void login() throws Exception {    MetricRegistry metrics = new MetricRegistry();    ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();    reporter.start(1, TimeUnit.SECONDS);
    metrics.register(MetricRegistry.name(MetricsGaugesTest.class, "logon","size"),              new Gauge<Integer>() {                  @Override                  public Integer getValue() {                      return tokenMap.size();                  }              });
     for (Map.Entry<String, String> entry : appMap.entrySet()) {         ApiLoginResult result = apiLoginService.login(entry.getKey(),entry.getValue());         if (result.getLogin()) {              tokenMap.put(result.getToken(),entry.getKey());              TimeUnit.SECONDS.sleep(1);         }     }  }}

复制代码

运行后输出:

17-11-23 15:37:31=============================================================
-- Gauges----------------------------------------------------------------------metrics.MetricsGaugesTest.logon.sizevalue = 0
17-11-23 15:37:32=============================================================
-- Gauges----------------------------------------------------------------------metrics.MetricsGaugesTest.logon.sizevalue = 0
17-11-23 15:37:33 =============================================================
-- Gauges----------------------------------------------------------------------metrics.MetricsGaugesTest.logon.sizevalue = 1

复制代码

3.1 MetricRegistry 层级结构

Metrics 监控的工具继承 Metric 接口,MetricRegistry 通过 ConcurrentMap 来管理监控工具的注册,注册时需要提供唯一限定名称和具体工具,限定名称可以使用 MetricRegistry 的静态方法 name 来生成,下面是 MetricRegistry 类重要的注册方法:

public <Textends Metric> Tregister(String name, T metric) throws IllegalArgumentException {  if (metric instanceof MetricSet) {    registerAll(name, (MetricSet) metric);}  else {    final Metric existing = metrics.putIfAbsent(name, metric);    if (existing == null) {      onMetricAdded(name, metric);    } else {      throw new IllegalArgumentException("A metric named " + name +" already exists");}}  return metric;}
private void registerAll(String prefix,MetricSet metrics) throws IllegalArgumentException {  for (Map.Entry<String, Metric> entry :metrics.getMetrics().entrySet()) {    if (entry.getValue() instanceof MetricSet) {      registerAll(name(prefix, entry.getKey()), (MetricSet) entry.getValue());    } else {      register(name(prefix, entry.getKey()), entry.getValue());    }  }}

复制代码

3.2 ConsoleReporter 层级结构

Metrics 需要指定监控的输出方式,本程序中使用了最简单的 ConsoleReporter,初始化 ConsoleReporter 需要设置统计的周期,这里设置的是 1s。

Reporter 最核心的是 ScheduledReporter 抽象基类,在这个抽象基类中定义了子类的构造方式,定义了通用的方法:开始统计、结束统计和根据不同工具产生的结果输出。

Reporter 之所以可以周期性的输出,实际上底层用到的就是 ScheduledExecutorService 类,通过 Executors.newSingleThreadScheduledExecutor 进行创建。

开始统计时,使用了 ScheduledExecutorService 的 scheduleAtFixedRate 方法,scheduleAtFixedRate 方法可以有效的保证方法执行的周期,上次运行不会影响后续的运行,即:scheduledExecutionTime(n)=firstExecuteTime+n*periodTime,计算方式永远不变。

最后让我们来看看 ConsoleReporter 是如何来实现控制台的输出,这里我们只需要关注 report 和 printGauge 即可,里面的 output 输出流实际上就是 System.out。

@Overridepublic void report(SortedMap<String,Gauge> gauges,SortedMap<String,Counter> counters,SortedMap<String,Histogram> histograms,SortedMap<String,Meter> meters,SortedMap<String,Timer> timers) {  final String dateTime = dateFormat.format(new Date(clock.getTime()));  printWithBanner(dateTime, '=');  output.println();
	if (!gauges.isEmpty()) {    printWithBanner("-- Gauges", '-');    for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {      output.println(entry.getKey());      printGauge(entry);}    	output.println();  }...}
private voidprintGauge(Map.Entry<String, Gauge> entry) {  output.printf(locale, "value = %s%n", entry.getValue().getValue());}

复制代码

3.3 Gauge 层级结构

Metrics 没有提供任何关于 Gauge 接口的实现,使用时我们需要自己实现,这里面需要注意的是变量可见性的问题,防止变量值的脏读。

4. 结束语

至此已经完成了 Gauge 的讲解,如果你正在思考如何保证服务的健壮,Metrics 是很好的选择。 下一章会介绍 Counters 的使用,文章中有任何纰漏的地方欢迎给予指出。



版权声明:本文为libingxin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。