使用 OpenTelemetry 检查 Spanner 组件中的延迟时间

本主题介绍了如何检查 Spanner 组件以查找延迟来源,以及如何使用 OpenTelemetry 直观呈现延迟时间。有关本主题中各组件的简要概述,请参见 Spanner 请求中的延迟时间点

OpenTelemetry 是一个开源可观测性框架和工具包, 您可以创建和管理遥测数据,例如跟踪记录、指标和日志。时间是 OpenTracing 和 OpenCensus 合并的结果。如需更多信息 请参阅什么是 OpenTelemetry?

Spanner 客户端库通过 使用 OpenTelemetry 可观测性框架。请按照 确定延迟时间点,以确定 Spanner 中显示延迟时间的组件或组件。

准备工作

在开始捕获延迟时间指标之前,请先熟悉手动 使用 OpenTelemetry 实现插桩。 您必须使用适当的选项配置 OpenTelemetry SDK,以导出遥测数据。有多个 有可用的 OpenTelemetry 导出器选项。我们建议使用 OpenTelemetry 协议 (OTLP) 导出器。 其他选项包括使用 OTel 收集器 使用 Google Cloud ExporterGoogle Managed Service for Prometheus Exporter

如果您在 Compute Engine 上运行应用,则可以使用 Ops Agent 用于收集 OpenTelemetry Protocol 指标和跟踪记录的代理。如需更多信息 请参阅收集 OTLP 指标和跟踪记录

添加依赖项

如需配置 OpenTelemetry SDK 和 OTLP 导出器,请添加以下内容 应用依赖项

Java

<dependency>
  <groupId>com.google.cloud</groupId>
  <artifactId>google-cloud-spanner</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk-metrics</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk-trace</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

Go

go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1
go.opentelemetry.io/otel/metric v1.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/sdk/metric v1.23.1

注入 OpenTelemetry 对象

然后,使用 OTLP 导出器创建一个 OpenTelemetry 对象,并注入 使用 SpannerOptions 的 OpenTelemetry 对象。

Java

// Enable OpenTelemetry metrics and traces before Injecting OpenTelemetry
SpannerOptions.enableOpenTelemetryMetrics();
SpannerOptions.enableOpenTelemetryTraces();

// Create a new meter provider
SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
    // Use Otlp exporter or any other exporter of your choice.
    .registerMetricReader(
        PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build())
    .build();

// Create a new tracer provider
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
    // Use Otlp exporter or any other exporter of your choice.
    .addSpanProcessor(SimpleSpanProcessor.builder(OtlpGrpcSpanExporter
        .builder().build()).build())
        .build();

// Configure OpenTelemetry object using Meter Provider and Tracer Provider
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
    .setMeterProvider(sdkMeterProvider)
    .setTracerProvider(sdkTracerProvider)
    .build();

// Inject OpenTelemetry object via Spanner options or register as GlobalOpenTelemetry.
SpannerOptions options = SpannerOptions.newBuilder()
    .setOpenTelemetry(openTelemetry)
    .build();
Spanner spanner = options.getService();

Go

// Ensure that your Go runtime version is supported by the OpenTelemetry-Go compatibility policy before enabling OpenTelemetry instrumentation.
// Refer to compatibility here https://github.com/googleapis/google-cloud-go/blob/main/debug.md#opentelemetry

import (
	"context"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"

	"cloud.google.com/go/spanner"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/metric"
	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
	"google.golang.org/api/iterator"
)

func enableOpenTelemetryMetricsAndTraces(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()

	// Create a new resource to uniquely identify the application
	res, err := newResource()
	if err != nil {
		log.Fatal(err)
	}

	// Enable OpenTelemetry traces by setting environment variable GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive value "opentelemetry" before loading the client library.
	// Enable OpenTelemetry metrics before injecting meter provider.
	spanner.EnableOpenTelemetryMetrics()

	// Create a new tracer provider
	tracerProvider, err := getOtlpTracerProvider(ctx, res)
	defer tracerProvider.ForceFlush(ctx)
	if err != nil {
		log.Fatal(err)
	}
	// Register tracer provider as global
	otel.SetTracerProvider(tracerProvider)

	// Create a new meter provider
	meterProvider := getOtlpMeterProvider(ctx, res)
	defer meterProvider.ForceFlush(ctx)

	// Inject meter provider locally via ClientConfig when creating a spanner client or set globally via setMeterProvider.
	client, err := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{OpenTelemetryMeterProvider: meterProvider})
	if err != nil {
		return err
	}
	defer client.Close()
	return nil
}

func getOtlpMeterProvider(ctx context.Context, res *resource.Resource) *sdkmetric.MeterProvider {
	otlpExporter, err := otlpmetricgrpc.New(ctx)
	if err != nil {
		log.Fatal(err)
	}
	meterProvider := sdkmetric.NewMeterProvider(
		sdkmetric.WithResource(res),
		sdkmetric.WithReader(sdkmetric.NewPeriodicReader(otlpExporter)),
	)
	return meterProvider
}

func getOtlpTracerProvider(ctx context.Context, res *resource.Resource) (*sdktrace.TracerProvider, error) {
	traceExporter, err := otlptracegrpc.New(ctx)
	if err != nil {
		return nil, err
	}

	tracerProvider := sdktrace.NewTracerProvider(
		sdktrace.WithResource(res),
		sdktrace.WithBatcher(traceExporter),
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
	)

	return tracerProvider, nil
}

func newResource() (*resource.Resource, error) {
	return resource.Merge(resource.Default(),
		resource.NewWithAttributes(semconv.SchemaURL,
			semconv.ServiceName("otlp-service"),
			semconv.ServiceVersion("0.1.0"),
		))
}

捕获和直观呈现客户端往返延迟时间

客户端往返延迟时间是指 客户端发送到的 Spanner API 请求的第一个字节 数据库(通过 GFE 和 Spanner API 前端), 以及客户端从数据库收到的响应的最后一个字节。

捕获客户端往返延迟时间

不支持 Spanner 客户端往返延迟时间指标 使用 OpenTelemetry您可以使用 OpenCensus 和 并将数据迁移到 OpenTelemetry

直观呈现客户端往返延迟时间

检索指标后,您可以 Cloud Monitoring。

下例中的图表展示了 客户端往返延迟时间指标如需将百分位延迟时间更改为 第 50 或第 99 百分位,使用聚合器菜单。

该程序会创建一个名为 roundtrip_latency 的视图。该字符串会成为 为指标名称指定名称。

Cloud Monitoring 客户端往返延迟时间。

捕获并直观呈现 GFE 延迟时间

Google Front End (GFE) 延迟时间是指 当 Google 网络收到来自客户端的远程过程调用时, 当 GFE 收到响应的第一个字节时。

捕获 GFE 延迟时间

您可以通过启用以下选项来捕获 GFE 延迟时间指标 使用 Spanner 客户端库。

Java

static void captureGfeMetric(DatabaseClient dbClient) {
  // GFE_latency and other Spanner metrics are automatically collected
  // when OpenTelemetry metrics are enabled.

  try (ResultSet resultSet =
      dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Go

// GFE_Latency and other Spanner metrics are automatically collected
// when OpenTelemetry metrics are enabled.
func captureGFELatencyMetric(ctx context.Context, client spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
	}
}

直观呈现 GFE 延迟时间

检索指标后,您可以在 Cloud Monitoring。

下面的图表示例展示了 的分布聚合 计算 GFE 延迟时间指标如需将百分位延迟时间更改为第 5 百分位, 第 50、第 95 或第 99 百分位:使用聚合器菜单。

该程序会创建一个名为 spanner/gfe_latency 的视图。这个 字符串导出到 Cloud Monitoring。

Cloud Monitoring GFE 延迟时间。

捕获并直观呈现 Spanner API 请求延迟时间

Spanner API 请求延迟时间是指时长(以秒为单位) 发送一个 Spanner API 收到的响应的最后一个字节 前端发送。

捕获 Spanner API 请求延迟时间

默认情况下,此延迟时间 Cloud Monitoring 指标。 您无需执行任何操作即可捕获和导出此数据。

直观呈现 Spanner API 请求延迟时间

您可以使用 Metrics Explorer 图表工具在 Cloud Monitoring 中直观呈现 spanner.googleapis.com/api/request_latencies 指标的图表。

下例中的图表展示了 Spanner API 请求延迟时间指标。如需更改百分位,请执行以下操作: 第 50 或第 99 百分位的延迟时间,请使用聚合器 菜单。

Cloud Monitoring API 请求延迟时间。

捕获并直观呈现查询延迟时间

查询延迟时间是指运行 SQL 所需的时长(以毫秒为单位) Spanner 数据库中的查询。

捕获查询延迟时间

您可以捕获以下语言的查询延迟时间:

Java

static void captureQueryStatsMetric(OpenTelemetry openTelemetry, DatabaseClient dbClient) {
  // Register query stats metric.
  // This should be done once before start recording the data.
  Meter meter = openTelemetry.getMeter("cloud.google.com/java");
  DoubleHistogram queryStatsMetricLatencies =
      meter
          .histogramBuilder("spanner/query_stats_elapsed")
          .setDescription("The execution of the query")
          .setUnit("ms")
          .build();

  // Capture query stats metric data.
  try (ResultSet resultSet = dbClient.singleUse()
      .analyzeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          QueryAnalyzeMode.PROFILE)) {

    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }

    String value = resultSet.getStats().getQueryStats()
        .getFieldsOrDefault("elapsed_time", Value.newBuilder().setStringValue("0 msecs").build())
        .getStringValue();
    double elapsedTime = value.contains("msecs")
        ? Double.parseDouble(value.replaceAll(" msecs", ""))
        : Double.parseDouble(value.replaceAll(" secs", "")) * 1000;
    queryStatsMetricLatencies.record(elapsedTime);
  }
}

Go

func captureQueryStatsMetric(ctx context.Context, mp metric.MeterProvider, client spanner.Client) error {
	meter := mp.Meter(spanner.OtInstrumentationScope)
	// Register query stats metric with OpenTelemetry to record the data.
	// This should be done once before start recording the data.
	queryStats, err := meter.Float64Histogram(
		"spanner/query_stats_elapsed",
		metric.WithDescription("The execution of the query"),
		metric.WithUnit("ms"),
		metric.WithExplicitBucketBoundaries(0.0, 0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0,
			16.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0, 80.0, 100.0, 130.0, 160.0, 200.0, 250.0,
			300.0, 400.0, 500.0, 650.0, 800.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0,
			100000.0),
	)
	if err != nil {
		fmt.Print(err)
	}

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithStats(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			// Record query execution time with OpenTelemetry.
			elapasedTime := iter.QueryStats["elapsed_time"].(string)
			elapasedTimeMs, err := strconv.ParseFloat(strings.TrimSuffix(elapasedTime, " msecs"), 64)
			if err != nil {
				return err
			}
			queryStats.Record(ctx, elapasedTimeMs)
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
	}
}

直观呈现查询延迟时间

检索指标后,您可以 Cloud Monitoring。

下面的图表示例展示了 的分布聚合 计算 GFE 延迟时间指标如需将百分位延迟时间更改为第 5 百分位, 第 50、第 95 或第 99 百分位:使用聚合器菜单。

该程序会创建一个名为 query_stats_elapsed 的 OpenCensus 视图。 当指标被导出到 Cloud Monitoring 时,该字符串会成为指标名称的一部分。

Cloud Monitoring 查询延迟时间。

后续步骤