论分布式系统中Metric框架的设计

前言


在复杂的分布式系统中,为了更好地了解系统的运行情况,往往我们会有多样的metric性能指标来帮助我们了解这里面的情况。在这其中的metric指标按照功能,还可以再分成多种不同类的指标类型,例如统计自增长类型的数据,或者统计均值速率型的等等。一个设计优良的metric收集模型无疑对于一个系统来说是十分重要的,本文我们来聊聊metric的框架设计的相关内容,对于一个系统而言,我们如何去设计一个简单又实用的metric框架模型。

Metric指标类型


如前面所提到的,不同的metric指标适用于不同的应用场景,因此我们需要了解一些常用的metric指标类型以及它的含义:

  • Counter,单调自增型统计数据,从系统启动的那一刻起,就开始进行技术统计了。
  • Gauge,实时测量值类型,这个指标获取的系统指标的当前实时值。
  • Average,区间时间段内的平均值。
  • Histogram,过去一定时间段内,指标数据的直方图分布情况,这个有点统计学的味道在里面了。

现在问题来了,对于上述四种情况,我们分别适用哪些场景呢?下面我们一个一个来看。

首先对于Counter纯计数类型的metric,因为它是单调递增型的,所以比较适用于那些同样具有单调递增属性的指标,比如transaction数,或者一些error发生次数的累计等等。

然后是Gauge类型的,此类型指标适用于中间状态存储的数据结构,因为它会有内部存储状态的实时变化,所以我们需要去了解其中的实时状态。具体一点的比如说,某个消息Queue的size指标,或者Cache的当前size等等。总而言之,此指标适用于状态瞬息万变的指标数据。

对于后两种的指标统计,它们偏重于单位时段内的数据分析,比较适用于系统内的RPC请求耗时调用统计。

设计一个优良的metric框架,了解Metric指标类型只是一个小小的背景前提。

Metric框架的设计


对于Metric框架设计而言,我们需要考虑的问题就要更多一些了,比如以下必须要解决的问题:

  • 我需要用哪些类型的Metric指标,收集哪些指标数据?
  • 应用层如何通过Metric框架来收集我们想要的metric指标?
  • 收集好后的Metric指标如何进行指标的expose?JMX,Console?

熟悉metric指标统计代码逻辑的同学,应该经常会看到metric registry的对象。在此节Metric框架模型的设计里,我们也会有Metric Registry对象,不过这里是一个大的集合类,MetricRegistries,在此集合汇总类里包含了各个小的metric注册类。因此这里首先会衍生出两个概念:

  • Metric Registry:此对象可针对各个不同服务级别层次,构造独立的metric registry,进而构造出具体的metric收集指标,进行指标的统计。
  • Metric Registry集合:此集合类是Metric Registry的管理类,管理了所有已经注册的Metric Registry对象,外部可以通过向此类查询得到之前创建的Metric Registry类。同样的Metric Registry对象只会维持一份,不过会有额外的引用计数属性。在实际场景内,会存在多个调用方获取Metric Registry进行metric收集。只有当Registry引用归为0后,此对象才会被移除。

在实际的使用场景中,这些Metric Registry对象首先将被初始化在具体使用类中,如下代码所示:

this.metricRegistry = LogServiceMetricsRegistry
        .createMetricRegistryForLogService(groupId.toString(), server.getId().toString());

然后通过metricRegistry进行metric变量的获取,如下timer指标即上面提到的Histogram含义的指标

this.readNextQueryTimer = metricRegistry.timer("readNextQueryTime");
    this.startIndexTimer= metricRegistry.timer("startIndexTime");
    this.sizeRequestTimer = metricRegistry.timer("sizeRequestTime");
    this.getStateTimer = metricRegistry.timer("getStateTime");
    this.lastIndexQueryTimer = metricRegistry.timer("lastIndexQueryTime");
    this.lengthQueryTimer = metricRegistry.timer("lengthQueryTime");
    this.syncRequesTimer = metricRegistry.timer("syncRequesTime");
    this.appendRequestTimer = metricRegistry.timer("appendRequestTime");
    this.getCloseLogTimer = metricRegistry.timer("getCloseLogTime");
    //archiving request time not the actual archiving time
    this.archiveLogRequestTimer = metricRegistry.timer("archiveLogRequestTime");
    this.archiveLogTimer = metricRegistry.timer("archiveLogTime");

最后在实际调用处,我们会进行metric指标的统计,

  @Override
  public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
    try {
      checkInitialization();
      final LogEntryProto entry = trx.getLogEntry();
      LogServiceRequestProto logServiceRequestProto =
          LogServiceRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
      switch (logServiceRequestProto.getRequestCase()) {
      case CHANGESTATE:
          return recordTime(getCloseLogTimer, new Task(){
            @Override public CompletableFuture<Message> run() {
              return processChangeState(logServiceRequestProto);
            }});
        case APPENDREQUEST:
          return recordTime(appendRequestTimer, new Task(){
              @Override public CompletableFuture<Message> run() {
                return processAppendRequest(trx, logServiceRequestProto);
              }});
        case SYNCREQUEST:
          return recordTime(syncRequesTimer, new Task(){
            @Override public CompletableFuture<Message> run() {
              return processSyncRequest(trx, logServiceRequestProto);
            }});
        case ARCHIVELOG:
          return updateArchiveLogInfo(logServiceRequestProto);
        default:
          //TODO
          return null;
      }
    } catch (IOException e) {
      // TODO exception handling
      throw new RuntimeException(e);
    }
  }

...
  // record time操作方法
  protected CompletableFuture<Message> recordTime(Timer timer, Task task) {
    final Timer.Context timerContext = timer.time();
    try {
      return task.run();
    } finally {
      timerContext.stop();
    }
  }

通过前面的过程,metric指标的收集过程全部完成了,但是这里还缺少重要的metric指标展示的部分,metric指标只有通过向外部暴露才能发挥其独有的作用。因此在metric框架中,额外需要metric report的功能模块,外部展示的方式可以是常用的JMX方式或者普通的Console终端展示模式。以下是简单的Console模式的metric输出report,

9/12/19 11:27:37 PM ============================================================

-- Gauges ----------------------------------------------------------------------
com.learn.gauge.freeMemory
             value = 234591968


9/12/19 11:27:38 PM ============================================================

-- Gauges ----------------------------------------------------------------------
com.learn.gauge.freeMemory
             value = 234591968


9/12/19 11:27:39 PM ============================================================

-- Gauges ----------------------------------------------------------------------
com.learn.gauge.freeMemory
             value = 234591968


9/12/19 11:27:40 PM ============================================================

-- Gauges ----------------------------------------------------------------------
com.learn.gauge.freeMemory
             value = 234591968

按照此小节的metric框架模型阐述,此结构图如下所示:

在这里插入图片描述

总而言之,Metric指标统计在复杂分布式系统中正扮演着越来越重要的角色。

发布了373 篇原创文章 · 获赞 403 · 访问量 203万+

猜你喜欢

转载自blog.csdn.net/Androidlushangderen/article/details/100752425