OTLP协议

1,364 阅读4分钟

OTLP(全称OpenTelemetry Protocol)是OpenTelemetry原生的遥测信号传递协议,虽然在OpenTelemetry的项目中组件支持了Zipkin或Jaeger的协议格式的实现,但是都是以第三方贡献库的形式提供的。只有OTLP是OpenTelemetry官方原生支持的格式。OTLP的数据模型定义是基于ProtoBuf完成的,如果实现一套可以收集OTLP遥测数据的后端服务,那就需要了解里面的内容,对应的代码仓库:opentelemetry-proto

OpenTelemetry协议的设计目标

  • 适合在以下节点类型之间使用:检测应用程序、遥测后端、本地代理、独立收集器/转发器
  • 数据交付可靠性高,数据无法交付时可见性清晰
  • 序列化和反序列化的CPU使用率较低
  • 对内存管理器施加最小的压力,包括直通场景,其中反序列化的数据是短暂的,必须在不久之后按原样序列化,以及此类短期数据的创建和丢弃频率很高
  • 支持高效修改反序列化数据并再次序列化以进一步传递的能力
  • 确保高延迟网络中的高吞吐量
  • 允许背压信号
  • 对负载平衡器友好

OpenTelemetry协议详情

OTLP定义了遥测数据的编码以及用于在客户端和服务器之间交换数据的协议 该规范定义了如何通过GRPC和HTTP 1.1传输实现OTLP,并指定了用于有效负载的ProtoBuf模式 OTLP是一种请求/响应式协议:客服端发送请求,服务器回复相应的响应 定义了一种请求和响应类型:Export 所有服务器组件必须支持以下传输压缩选项:

  • none:无压缩
  • gzip:Gzip压缩

基于OTLP协议使用开发接收器

OTLP/HTTP

OTLP/HTTP使用HTTP POST请求将遥测数据从客户端发送到服务器。实现可以使用HTTP/1.1或HTTP/2传输 如果没有设置专门针对各种可观测类别请求URL路径,那么有以下路径默认配置:

  • Trace:v1/traces
  • Metric:v1/metrics
  • Log:v1/logs

示例:如果路径配置为OTEL_EXPORTER_OTLP_ENDPOINT**=**http://collector:4318,那么Trace发送到http://collector:4318/v1/traces,度量发送到http://collector:4318/v1/metrics,日志发送到http://collector:4318/v1/logs

请求规范

携带跟踪数据的请求默认URL路径是/v1/traces,请求正文是ProtoBuf编码的ExportTraceServiceRequest消息 客户端必须设置Content-Type: application/x-protobuf请求头。客户端可以gzip内容,这种情况下应该包含Content-Encoding: gzip请求头。如何客户端可以接收gzip编码的响应,则客户端可以包含Accept-Encoding: gzip请求标头

响应规范

成功后服务器HTTP 200 OK。响应正文必须是ExportTraceServiceResponse用于Trace的ProtoBuf编码消息 服务器必须设置Content-Type: application/x-protobuf响应头。如果请求头Accept-Encoding: gzip出现在请求中,服务器可以对Response进行gzip编码并设置Content-Encoding: gzip响应头 接收方法只需要是一个POST请求,接收二进制数据:

<!-- Proto,无需手动将Proto文件转换成JavaBean,直接引入官方提供的依赖即可 -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-proto</artifactId>
    <version>1.6.0-alpha</version>
</dependency>
@PostMapping("/v1/traces")
public ExportTraceServiceResponse traceReceive(@RequestBody byte[] data) throws InvalidProtocolBufferException {
    ExportTraceServiceRequest exportTraceServiceRequest = 
        ExportTraceServiceRequest.parseFrom(data);
    return ExportTraceServiceResponse.getDefaultInstance();
}

OTLP/GRPC

建立底层GRPC传输后,客户端开始使用Export*ServiceRequest消息,发送遥测数据,客户端不断向服务器发送一系列请求,并期望收到对每个请求的响应

用于日志的消息 ExportLogsServiceRequest 用于指标的消息 ExportMetricsServiceRequest 用于跟踪的消息 ExportTraceServiceRequest

注意:此协议关注一对客户端/服务端节点之间传递的可靠性,旨在确保客户端和服务器之间的传输过程中不会丢失任何数据。许多遥测收集系统具有数据必须经过的中间节点或代理,此类系统中的端到端交付保证超出了OTLP的范围。本协议中描述的确认发生在单个客户端/服务器对之间,并且不跨越传递路径中的中间节点

<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-proto</artifactId>
  <version>1.6.0-alpha</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty-shaded</artifactId>
  <version>1.44.1</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.44.1</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.44.1</version>
</dependency>
package com.yss;

import com.yss.service.LogsServiceImpl;
import com.yss.service.MetricsServiceImpl;
import com.yss.service.TraceServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

public class GrpcServer {
    private static final Logger logger = Logger.getLogger(GrpcServer.class.getName());

    private Server server;

    private void start() throws IOException {
        int port = 8080;
        server = ServerBuilder.forPort(port)
                .addService(new LogsServiceImpl())
                .addService(new MetricsServiceImpl())
                .addService(new TraceServiceImpl())
                .build()
                .start();
        logger.info("Server started, listening on " + port);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.err.println("*** shutting down gRPC server since JVM is shutting down");
            try {
                GrpcServer.this.stop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.err.println("*** server shut down");
        }));
    }

    private void stop() throws InterruptedException {
        if (server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    // 由于GRPC库使用守护程序线程,因此在主线程上等待终止
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        final GrpcServer grpcServer = new GrpcServer();
        grpcServer.start();
        grpcServer.blockUntilShutdown();
    }
}

package com.yss.service;

import com.yss.GrpcServer;
import io.grpc.stub.StreamObserver;
import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest;
import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse;
import io.opentelemetry.proto.collector.logs.v1.LogsServiceGrpc;

import java.util.logging.Logger;

public class LogsServiceImpl extends LogsServiceGrpc.LogsServiceImplBase {

    private static final Logger logger = Logger.getLogger(GrpcServer.class.getName());

    @Override
    public void export(ExportLogsServiceRequest request, StreamObserver<ExportLogsServiceResponse> responseObserver) {
        logger.info(request.toString());
        responseObserver.onNext(ExportLogsServiceResponse.newBuilder().build());
        responseObserver.onCompleted();
    }
}

package com.yss.service;

import com.yss.GrpcServer;
import io.grpc.stub.StreamObserver;
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse;
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc;

import java.util.logging.Logger;

public class MetricsServiceImpl extends MetricsServiceGrpc.MetricsServiceImplBase {

    private static final Logger logger = Logger.getLogger(GrpcServer.class.getName());

    @Override
    public void export(ExportMetricsServiceRequest request, StreamObserver<ExportMetricsServiceResponse> responseObserver) {
        logger.info(request.toString());
        responseObserver.onNext(ExportMetricsServiceResponse.newBuilder().build());
        responseObserver.onCompleted();
    }
}

package com.yss.service;

import com.yss.GrpcServer;
import io.grpc.stub.StreamObserver;
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse;
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc;

import java.util.logging.Logger;

public class TraceServiceImpl extends TraceServiceGrpc.TraceServiceImplBase {

    private static final Logger logger = Logger.getLogger(GrpcServer.class.getName());

    @Override
    public void export(ExportTraceServiceRequest request, StreamObserver<ExportTraceServiceResponse> responseObserver) {
        logger.info(request.toString());
        responseObserver.onNext(ExportTraceServiceResponse.newBuilder().build());
        responseObserver.onCompleted();
    }
}