写点什么

深入理解 Metrics(一):Gauges

作者:冰心的小屋
  • 2022-11-07
    上海
  • 本文字数:3191 字

    阅读完需:约 10 分钟

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 的使用,文章中有任何纰漏的地方欢迎给予指出。

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2013-08-06 加入

还未添加个人简介

评论

发布
暂无评论
深入理解Metrics(一):Gauges_Java_冰心的小屋_InfoQ写作社区