怎么理解metrics (深入理解elasticsearch)

1. 介绍

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

Metrics 提供监控的工具:

  • Gauges:指标当前值。
  • Counters:指标自增自减。
  • Histograms: 指标分布情况,最大、最小、TP99 等。
  • Meters:指标频率,例如 TPS。
  • Timers:Histograms 和 Meters 结合使用。
  • Health Checks:服务健康状况监控。

指标结果输出方式:

深入理解gogpm,深入理解overlayfs

  • 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 层级结构

深入理解gogpm,深入理解overlayfs

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 层级结构

深入理解gogpm,深入理解overlayfs

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

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

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

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

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

@Override
public 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 层级结构

深入理解gogpm,深入理解overlayfs

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

4. 结束语

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