使用 OpenCensus 指标

本页面介绍了为可用性和延迟时间 SLI 创建 OpenCensus 指标的基础知识。它还提供了有关如何使用 OpenCensus 指标定义 SLO 的实现示例。

OpenCensus 基础知识

OpenCensus 是库的单一开源分发,可在 OpenCensus GitHub 页面上找到,它可以自动收集跟踪记录和指标并将它们发送到任何后端。OpenCensus 可用于检测您的服务,以发出可提取到 Cloud Monitoring 的自定义指标。然后,您可以将这些指标用作 SLI。

如需查看有关使用 OpenCensus 创建非专门用作 SLI 的 Monitoring 指标的示例,请参阅利用 OpenCensus 获取自定义指标

指标

如需使用 OpenCensus 从服务收集指标数据,您必须使用以下 OpenCensus 构造:

  • Measure:表示要记录的指标类型,用指标名称指定。Measure 可以记录 Int64 或 Float64 值。
  • Measurement:记录 Measure 为特定事件收集和写入的特定数据点。例如,Measurement 可记录特定响应的延迟时间。
  • View:指定应用于 Measure 的聚合。OpenCensus 支持以下聚合类型:
    • 计数:测量点计数。
    • 分布:测量点的直方图分布。
    • 总和:测量值的总和。
    • LastValue:测量记录的最后一个值。

如需了解详情,请参阅 OpenCensus 统计信息/指标。请注意,OpenCensus 通常将指标称为"统计信息"

检测

OpenCensus 库有多种语言版本。如需查看有关检测服务以发出指标的特定于语言的信息,请参阅 OpenCensus 语言支持。此外,利用 OpenCensus 获取自定义指标提供了 Monitoring 常用的语言的示例。

在基本情况下,您需要执行以下操作:

  • 对服务进行插桩,以记录和导出指标。
  • 定义导出器以接收指标。

对于每个指标,您需要定义 Measure 来指定值类型:Int64 或 Float64。您还需要定义并注册 View 来指定聚合类型(计数、分布、总和或 last-value)。如需使用分布聚合类型,您还需要明确指定直方图分桶边界。您还需要在 View 中为指标指定名称。

导出器

最后,您需要使用导出器来收集指标并将它们写入 Cloud Monitoring 或其他后端。如需了解可用于 Monitoring 的语言特定的导出器,请参阅 OpenCensus 导出器

您还可以编写自己的导出器;如需了解详情,请参阅编写自定义导出器

为 SLI 创建指标

您的应用必须创建可用作 Cloud Monitoring 中的 SLI 的 OpenCensus 指标:

  • 对于针对请求和错误计数的可用性 SLI,可以将 Measure 与计数聚合结合使用。
  • 对于延迟时间 SLI,可以将 Measure 与分布聚合结合使用。

可用性 SLI 的指标

您可以通过使用 TimeSeriesRatio 结构设置“正常”或“错误”请求数与请求总数的比率,在 Cloud Monitoring API 中表示基于请求的可用性 SLI。此比率用于 RequestBasedSli 结构的 goodTotalRatio 字段。

您的应用必须创建可用于构造此比率的 OpenCensus 指标。在您的应用中,您必须至少创建以下指标中的两个:

  1. 统计事件总数的指标;此指标用于比率的 totalServiceFilter

    您可以使用计数聚合创建类型为 Int64 的 OpenCensus 指标,从而为每个收到的请求记录值 1

  2. 统计“错误”事件数的指标,此指标用于比率的 badServiceFilter

    您可以使用计数聚合创建类型为 Int64 的 OpenCensus 指标,从而为每个错误或失败的请求记录值 1

  3. 统计“正常”事件数的指标,此指标用于比率的 goodServiceFilter

    您可以使用计数聚合创建类型为 Int64 的 OpenCensus 指标,从而为每个成功响应记录值 1

延迟时间 SLI 的指标

您可以通过使用 DistributionCut 结构,在 Cloud Monitoring API 中表示基于请求的延迟时间 SLI。此结构用于 RequestBasedSli 结构的 distributionCut 字段。

您可以使用分布聚合类型通过 View 创建 Int64 或 Float64 Measure。您还必须明确定义分桶边界。请注意,采用让您能够精确衡量所需阈值内的请求所占百分比的方式定义分桶非常重要。如需了解本主题,请参阅站点可靠性工程工作簿中的实现 SLO

实现示例

本部分介绍了一个在 Node.js 中使用 OpenCensus 实现基本可用性和延迟时间 SLI 的指标。

检测

如需使用 OpenCensus 对服务进行插桩以发出指标,请执行以下操作:

  1. 添加必要的库:

    Go

    import (
    	"flag"
    	"fmt"
    	"log"
    	"math/rand"
    	"net/http"
    	"time"
    
    	"contrib.go.opencensus.io/exporter/stackdriver"
    	"go.opencensus.io/stats"
    	"go.opencensus.io/stats/view"
    	"go.opencensus.io/tag"
    )
    

    Node.js

    // opencensus setup
    const {globalStats, MeasureUnit, AggregationType} = require('@opencensus/core');
    const {StackdriverStatsExporter} = require('@opencensus/exporter-stackdriver');

    Python

    from flask import Flask
    from opencensus.ext.prometheus import stats_exporter as prometheus
    from opencensus.stats import aggregation as aggregation_module
    from opencensus.stats import measure as measure_module
    from opencensus.stats import stats as stats_module
    from opencensus.stats import view as view_module
    from opencensus.tags import tag_map as tag_map_module
    
    from prometheus_flask_exporter import PrometheusMetrics
    
  2. 定义并注册导出器:

    Go

    // Sets up Cloud Monitoring exporter.
    sd, err := stackdriver.NewExporter(stackdriver.Options{
    	ProjectID:         *projectID,
    	MetricPrefix:      "opencensus-demo",
    	ReportingInterval: 60 * time.Second,
    })
    if err != nil {
    	log.Fatalf("Failed to create the Cloud Monitoring exporter: %v", err)
    }
    defer sd.Flush()
    
    sd.StartMetricsExporter()
    defer sd.StopMetricsExporter()

    Node.js

    // Stackdriver export interval is 60 seconds
    const EXPORT_INTERVAL = 60;
    const exporter = new StackdriverStatsExporter({
      projectId: projectId,
      period: EXPORT_INTERVAL * 1000,
    });
    globalStats.registerExporter(exporter);

    Python

    def setup_openCensus_and_prometheus_exporter() -> None:
        stats = stats_module.stats
        view_manager = stats.view_manager
        exporter = prometheus.new_stats_exporter(prometheus.Options(namespace="oc_python"))
        view_manager.register_exporter(exporter)
        register_all_views(view_manager)
  3. 为每个指标定义一个 Measure

    Go

    // Sets up metrics.
    var (
    	requestCount       = stats.Int64("oc_request_count", "total request count", "requests")
    	failedRequestCount = stats.Int64("oc_failed_request_count", "count of failed requests", "requests")
    	responseLatency    = stats.Float64("oc_latency_distribution", "distribution of response latencies", "s")
    )
    

    Node.js

    const REQUEST_COUNT = globalStats.createMeasureInt64(
      'request_count',
      MeasureUnit.UNIT,
      'Number of requests to the server'
    );
    const ERROR_COUNT = globalStats.createMeasureInt64(
      'error_count',
      MeasureUnit.UNIT,
      'Number of failed requests to the server'
    );
    const RESPONSE_LATENCY = globalStats.createMeasureInt64(
      'response_latency',
      MeasureUnit.MS,
      'The server response latency in milliseconds'
    );

    Python

    m_request_count = measure_module.MeasureInt(
        "python_request_count", "total requests", "requests"
    )
    m_failed_request_count = measure_module.MeasureInt(
        "python_failed_request_count", "failed requests", "requests"
    )
    m_response_latency = measure_module.MeasureFloat(
        "python_response_latency", "response latency", "s"
    )
  4. 使用适当的聚合类型为每个 Measure 以及响应延迟时间和分桶边界定义并注册 View

    Go

    // Sets up views.
    var (
    	requestCountView = &view.View{
    		Name:        "oc_request_count",
    		Measure:     requestCount,
    		Description: "total request count",
    		Aggregation: view.Count(),
    	}
    	failedRequestCountView = &view.View{
    		Name:        "oc_failed_request_count",
    		Measure:     failedRequestCount,
    		Description: "count of failed requests",
    		Aggregation: view.Count(),
    	}
    	responseLatencyView = &view.View{
    		Name:        "oc_response_latency",
    		Measure:     responseLatency,
    		Description: "The distribution of the latencies",
    		// Bucket definitions must be explicitly specified.
    		Aggregation: view.Distribution(0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000),
    	}
    )
    
    	// Register the views.
    	if err := view.Register(requestCountView, failedRequestCountView, responseLatencyView); err != nil {
    		log.Fatalf("Failed to register the views: %v", err)
    	}

    Node.js

    const request_count_metric = globalStats.createView(
      'request_count_metric',
      REQUEST_COUNT,
      AggregationType.COUNT
    );
    globalStats.registerView(request_count_metric);
    const error_count_metric = globalStats.createView(
      'error_count_metric',
      ERROR_COUNT,
      AggregationType.COUNT
    );
    globalStats.registerView(error_count_metric);
    const latency_metric = globalStats.createView(
      'response_latency_metric',
      RESPONSE_LATENCY,
      AggregationType.DISTRIBUTION,
      [],
      'Server response latency distribution',
      // Latency in buckets:
      [0, 1000, 2000, 3000, 4000, 5000, 10000]
    );
    globalStats.registerView(latency_metric);

    Python

    # set up views
    latency_view = view_module.View(
        "python_response_latency",
        "The distribution of the latencies",
        [],
        m_response_latency,
        aggregation_module.DistributionAggregation(
            [0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
        ),
    )
    
    request_count_view = view_module.View(
        "python_request_count",
        "total requests",
        [],
        m_request_count,
        aggregation_module.CountAggregation(),
    )
    
    failed_request_count_view = view_module.View(
        "python_failed_request_count",
        "failed requests",
        [],
        m_failed_request_count,
        aggregation_module.CountAggregation(),
    )
    
    # register views
    def register_all_views(view_manager: stats_module.stats.view_manager) -> None:
        view_manager.register_view(latency_view)
        view_manager.register_view(request_count_view)
        view_manager.register_view(failed_request_count_view)
  5. “请求计数”和“错误计数”指标的记录值:

    Go

    // Counts the request.
    stats.Record(ctx, requestCount.M(1))
    
    // Randomly fails 10% of the time.
    if rand.Intn(100) >= 90 {
    	// Counts the error.
    	stats.Record(ctx, failedRequestCount.M(1))

    Node.js

    // record a request count for every request
    globalStats.record([
      {
        measure: REQUEST_COUNT,
        value: 1,
      },
    ]);
    
    // randomly throw an error 10% of the time
    const randomValue = Math.floor(Math.random() * 9 + 1);
    if (randomValue === 1) {
      // Record a failed request.
      globalStats.record([
        {
          measure: ERROR_COUNT,
          value: 1,
        },
      ]);

    Python

    mmap = stats_recorder.new_measurement_map()
    # count request
    mmap.measure_int_put(m_request_count, 1)
    # fail 10% of the time
    if random.randint(0, 100) > 90:
        mmap.measure_int_put(m_failed_request_count, 1)
        tmap = tag_map_module.TagMap()
        mmap.record(tmap)
        return ("error!", 500)
  6. 记录延迟时间值:

    Go

    requestReceived := time.Now()
    // Records latency for failure OR success.
    defer func() {
    	stats.Record(ctx, responseLatency.M(time.Since(requestReceived).Seconds()))
    }()

    Node.js

    globalStats.record([
      {
        measure: RESPONSE_LATENCY,
        value: stopwatch.elapsedMilliseconds,
      },
    ]);

    Python

    start_time = time.perf_counter()
    mmap = stats_recorder.new_measurement_map()
    if random.randint(0, 100) > 90:
        response_latency = time.perf_counter() - start_time
        mmap.measure_float_put(m_response_latency, response_latency)
        tmap = tag_map_module.TagMap()
        mmap.record(tmap)

提取的指标

在指标导出到 Cloud Monitoring 后,这些指标显示为前缀指示指标源于 OpenCensus 的指标类型。例如,Node.js 实现中每个 OpenCensus View 的名称的对应关系如下所示:

  • request_count_sli 变为 custom.googleapis.com/opencensus/request_count_sli
  • error_count_sli 变为 custom.googleapis.com/opencensus/error_count_sli
  • response_latency_sli 变为 custom.googleapis.com/opencensus/response_latency_sli

服务运行后,您可以在 Metrics Explorer 中搜索指标,以确认这些指标是否被提取到 Monitoring 中。

可用性 SLI

在 Cloud Monitoring 中,您可以通过使用 TimeSeriesRatio 结构来表示基于请求的可用性 SLI。以下示例展示了一个 SLO,该 SLO 使用提取的 OpenCensus 指标并预计服务的可用性为 98%(根据在一个滚动的 28 天时间段内,error_count_slirequest_count_sli 的比率计算得出)。

{
  "serviceLevelIndicator": {
    "requestBased": {
      "goodTotalRatio": {
        "totalServiceFilter":
          "metric.type=\"custom.googleapis.com/opencensus/request_count_sli\",
       "badServiceFilter":
          "metric.type=\"custom.googleapis.com/opencensus/error_count_sli\"
      }
    }
  },
  "goal": 0.98,
  "rollingPeriod": "2419200s",
  "displayName": "98% Availability, rolling 28 days"
}

延迟时间 SLI

在 Cloud Monitoring 中,您可以通过使用 DistributionCut 结构来表示基于请求的延迟时间 SLI。以下示例展示了一个 SLO,该 SLO 使用提取的 OpenCensus 延迟时间指标,并预计在一个滚动的一天时间段内,98% 的请求在 1000 毫秒内完成:

{
  "serviceLevelIndicator": {
    "requestBased": {
      "distributionCut": {
        "distributionFilter":
          "metric.type=\"custom.googleapis.com/opencensus/response_latency_sli\",
        "range": {
          "min": 0,
          "max": 1000
        }
      }
    }
  },
  "goal": 0.98,
  "rollingPeriod": "86400s",
  "displayName": "98% requests under 1000 ms"
}