分布式跟踪平台OpenTelemetry初体验
背景
什么是分布式跟踪平台
现代的软件系统,均采用了微服务的分布式架构。其解决了弹性和可伸缩性的问题,但是带来了监控和调试的问题,即可观察性(可观测性)的问题。
分布式跟踪平台,帮助各种规模的公司,在高度分散的环境中,监控其软件系统并对其进行故障排除[4]。
下面是一个形象的理解[5]。
系统的交互行为可以分为几种形态:
- 系统内部
- 组件功能闭环,不与其他组件或系统交互
- 组件之间交互
- 系统之间
- 系统和系统之间进行交互
这样,若想通过系统的外部输出了解系统的状态,就需要两种形态的信息:
- 组件闭环的信息
- 组件间或系统间流动的信息
第一种形态通常可通过 logs 或 metrics 表征,第二种形态就需要 trace 表征,在流动的信息中增加标记。
对于 logs 和 metrics 的区别,可通过它们的操作方法进行理解。
再进一步抽象,可观测性涉及到如下问题:
- 观测数据的数据模型
- 观测数据的采集
- 观测数据的处理
- 观测数据的导出
- 观测数据的使用
- etc.
上述即是 OpenTelemetry 面对的问题域及具体的问题,且将具体的问题限定在:
- 观测数据的数据模型
- 观测数据的采集
- 观测数据的处理
- 观测数据的导出
平台实现有哪些
分布式跟踪系统,可以使用OpenTelemetry规范进行观察,跟踪和记录用户请求信息以及它们通过的系统。
OpenTelemetry 是 CNCF 的一个可观测性项目,旨在提供可观测性领域的标准化方案,解决观测数据的数据模型、采集、处理、导出等的标准化问题,提供与三方 vendor 无关的服务[5]。
Jaeger是OpenTelemetry的一个实体。Jaeger项目由Uber公司于2015年创建,目前被整合至不计其数的微服务架构当中,每秒负责记录成千上万条企业业务记录。
APM的三大模块分别是集中式日志系统,集中式度量系统和分布式全链接追踪系统。
而Jaeger
属于的就是追踪系统,度量系统我们则会使用prometheus
,日志系统一般则是ELK
。
Jaeger和Zipkin选哪个
[4] 分布式跟踪平台Jaeger和Zipkin,选哪个?
Zipkin
Zipkin早于Jaeger,是Google Dapper的开源版本,由Twitter进一步开发。Zipkin基于Java语言的应用程序,其中包含很多服务,每个服务都实现Zipkin具体的某一个功能,并包括一个用户界面和用于跟踪软件系统框架的界面。每个服务还提供了一系列存储引擎来持久存储数据,例如内存数据库,MySQL,Cassandra和Elasticsearch。
此外,Zipkin还提供了传输机制(如RabbitMQ,Scribe,HTTP和Kafka)以及用于在Cassandra中存储数据的基于节点的服务器。Zipkin支持大多数流行的高级语言,包括C#,Java和JavaScript。
Jaeger
Jaeger由Uber创建,并用Go语言编写。它除了Zipkin的功能集外,Jaeger还提供了动态采样,REST API,基于ReactJS的UI界面,以及对Cassandra和Elasticsearch内存数据存储的支持。为了实现这些功能,Jaeger相比Zipkin采取了一种不同的,更分散的方法。
比较
Jeager优点:
- 兼容OpenTracing API,写起来简单方便。
- UI相较于Zipkin的更加直观和丰富
- 还有一个则是sdk比较丰富,go语言编写,上传采用的是udp传输,效率高速度快。
Pinpoint
相比Pinpoint的缺点,当然是UI差距了,基本上现在流行的追踪系统UI上都远远逊于它。
使用
有两种部署方式。一是将collector放在APP外部,二是将collector放在APP内部。
概况
- Jaeger服务端:
- Skywalking服务端:可选
Jaeger服务端
opentelemetry-all-in-one
搭建一个otel-all-in-one的jaeger服务端[18]
docker pull jaegertracing/opentelemetry-all-in-one
启动
docker run --name jaeger -p 13133:13133 -p 16686:16686 -p 4317:55680 -d --restart=unless-stopped jaegertracing/opentelemetry-all-in-one
启动后,可以通用 http://localhost:16686/search
来访问web-ui
相关端口:
- 4317:Open Telemetry Protocol (OTLP) receiver 端口,用来收集Open Telemetry格式数据。
- 16686:仪表盘。
- 13133:Jaeger健康检查端口。
all-in-one(略, 14250不工作)
通过Docker 快速搭建Jaeger本地测试、开发联调环境[6]
docker run -d -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:1.18
这个镜像很小,只有58MB。
端口说明:
端口 | 协议 | 功能 |
---|---|---|
14250 | gRPC | 由jaeger Agent 用来发送范围模型.proto格式 |
14268 | HTTP | 可以接收直接从客户端发来的Span, 以 Jaeger Thrift格式,通过二进制Trift协议上传 |
9411 | HTTP | 可以接收 Thrift, JSON and Proto 格式的ZipKin Span (默认是禁用的) |
14269 | HTTP | 管理端口,可用于健康检查(API: /)和 指标采集 (/metrics) |
启动后,可以通用 http://localhost:16686/search
来访问web-ui [8]。
Skywalking服务端(可选)
windows下,需要设备下docker-desktop的参数[14]:
wsl -d docker-desktop
sysctl -w vm.max_map_count=262144
exit
搭建一个快速测试的环境[15]。
准备镜像:
docker pull docker.elastic.co/elasticsearch/elasticsearch:6.8.13
docker pull apache/skywalking-base:8.3.0-es6
docker pull apache/skywalking-oap-server:8.3.0-es6
docker pull apache/skywalking-ui:8.3.0
启动镜像:
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -d docker.elastic.co/elasticsearch/elasticsearch:6.8.13
docker run --name skywalking-oap --restart always -p 1234:1234 -p 11800:11800 -p 12800:12800 -d --link elasticsearch:elasticsearch -e SW_STORAGE=elasticsearch -e SW_STORAGE_ES_CLUSTER_NODES=elasticsearch:9200 apache/skywalking-oap-server:8.3.0-es6
docker run --name skywalking-ui --restart always -p 8080:8080 --link skywalking-oap:skywalking-oap -d -e SW_OAP_ADDRESS=skywalking-oap:12800 apache/skywalking-ui
agent和collector(可选)
由于jaeter all-in-one中已经有agent和collector了,所以本节可选。
agent是一个sidecar/host agent,它以标准格式接收来自客户端库的遥测数据,并将其转发给collector;[12]
collector将数据转换为特定跟踪后端可以理解的格式,并将其发送到那里。OpenCensus收集器还能够执行基于尾部的采样。
[13]
OpenTelemetry Collector的功能就是将SDK及Agent中收集的数据,经过数据处理后转发到对应的数据存储端及分析端中。数据会使用Receiver做为采集端,Exporter作为数据的输出端,Processor则在中间进行数据处理。
Java客户端
名词解释[12]
- Span:一次请求的一个具体操作,比如远程调用入口或者内部方法调用。
- SpanContext:一次请求追踪的上下文,用于关联该次请求下的具体操作。
- Attribute:Span的附加属性字段,用于记录关键信息。
OpenTelemetry的Span可以分为三类:
- 入口Span:会创建新的SpanContext,例如Server、Consumer。
- 内部Span:会复用已经创建的SpanContext,作为内部方法栈记录。
- 出口Span:会将SpanContext透传下去,例如Client、Producer。
字节码方式
官方 opentelemetry-javaagent.jar
中支付了常用的库/框架和应用服务器[16, 17],如下:
Library/Framework Versions
Akka HTTP 10.0+
Apache HttpAsyncClient 4.1+
Apache HttpClient 2.0+
Armeria 1.3+
AsyncHttpClient 1.9+ (not including 2.x yet)
AWS Lambda 1.0+
AWS SDK 1.11.x and 2.2.0+
Cassandra Driver 3.0+
Couchbase Client 2.0+ (not including 3.x yet)
Dropwizard Views 0.7+
Elasticsearch API 5.0+ (not including 7.x yet)
Elasticsearch REST Client 5.0+
Finatra 2.9+
Geode Client 1.4+
Google HTTP Client 1.19+
Grizzly 2.0+ (disabled by default)
gRPC 1.5+
Hibernate 3.3+
HttpURLConnection Java 7+
http4k † 3.270.0+
Hystrix 1.4+
JAX-RS 0.5+
JAX-RS Client 2.0+
JDBC Java 7+
Jedis 1.4+
JMS 1.1+
JSP 2.3+
Kafka 0.11+
khttp 0.1+
Kubernetes Client 7.0+
Lettuce 4.0+ (not including 6.x yet)
Log4j 1 1.2+
Log4j 2 2.7+
Logback 1.0+
MongoDB Drivers 3.3+
Netty 3.8+
OkHttp 3.0+
Play 2.3+ (not including 2.8.x yet)
Play WS 1.0+
RabbitMQ Client 2.7+
Ratpack 1.4+
Reactor 3.1+
Reactor Netty 0.9+ (not including 1.0)
Rediscala 1.8+
Redisson 3.0+
RMI Java 7+
RxJava 1.0+
Servlet 2.2+
Spark Web Framework 2.3+
Spring Batch 3.0+
Spring Data 1.8+
Spring Scheduling 3.1+
Spring Web MVC 3.1+
Spring Webflux 5.0+
Spymemcached 2.12+
Struts2 2.3+
Twilio 6.6+ (not including 8.x yet)
Vert.x 3.0+
Vert.x RxJava2 3.5+
和
Glassfish 5.0.x, 5.1.x OpenJDK 8, 11 Ubuntu 18, Windows Server 2019
JBoss EAP 7.1.x, 7.3.x OpenJDK 8, 11 Ubuntu 18, Windows Server 2019
Jetty 9.4.x, 10.0.x, 11.0.x OpenJDK 8, 11 Ubuntu 20
Payara 5.0.x, 5.1.x OpenJDK 8, 11 Ubuntu 18, Windows Server 2019
Tomcat 7.0.x, 8.5.x, 9.0.x, 10.0.x OpenJDK 8, 11 Ubuntu 18
Weblogic 12 OpenJDK 8 Oracle Linux 7, 8
Weblogic 14 OpenJDK 8, 11 Oracle Linux 7, 8
WildFly 13.0.x OpenJDK 8 Ubuntu 18, Windows Server 2019
WildFly 17.0.1, 21.0.0 OpenJDK 8, 11 Ubuntu 18, Windows Server 2019
可以通过命令行启用,如发送到jaeger如下[19]:
java -javaagent:opentelemetry-javaagent.jar -Dotel.exporter=jaeger -Dotel.exporter.jaeger.endpoint=127.0.0.1:14250 -Dotel.otlp.span.timeout=4000 -Dotel.resource.attributes=service.name=otel-demo -jar target/otel-demo-0.0.1-SNAPSHOT.jar
结果:
在Jaeger UI中可以看到 Service:otel-demo,以及每个请求的span信息。
也可以打开调试模式:
-Dotel.javaagent.debug=true
SDK代码方式
POM依赖[12]
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<opentelemetry.version>1.10.0</opentelemetry.version>
</properties>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-logging</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
</dependencies>
代码示例
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MainHelloWorld {
private static ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1);
private static Tracer tracer;
public static void main(String[] args) {
OtlpGrpcSpanExporter grpcSpanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("http://127.0.0.1:4317")
.build();
// 输出在日志中
// SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
// .addSpanProcessor(BatchSpanProcessor.builder(new LoggingSpanExporter()).build())
// .build();
// 输出在exporter中
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(grpcSpanExporter).build())
.setResource(Resource.create(Attributes.builder()
.put("service.name", "otel-demo-sdk")
// .put(ResourceAttributes.SERVICE_NAMESPACE, "${service.namespace}")
// .put(ResourceAttributes.SERVICE_VERSION, "${version}")
// .put(ResourceAttributes.HOST_NAME, "${host}")
.build()))
.build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.buildAndRegisterGlobal();
tracer = openTelemetry.getTracer("manual-sdk", "1.0.0");
executorService.scheduleAtFixedRate(() -> {
Span span = tracer.spanBuilder("schedule")
.setAttribute("schedule.time", System.currentTimeMillis())
.startSpan();
try (Scope scope = span.makeCurrent()) {
System.out.println("Hello world");
Thread.sleep(3000L);
span.setAttribute("schedule.success", true);
} catch (Throwable t) {
span.setStatus(StatusCode.ERROR, t.getMessage());
} finally {
span.end();
}
}, 5, 5, TimeUnit.SECONDS);
}
}
参考
- OpenTelemetry Documentation
- CNCF宣布Jaeger项目已正式毕业
- 全链路监控Jaeger搭建实战
- Jaeger与Zipkin:分布式跟踪平台该选谁
- OpenTelemetry 简析
- Docker快速搭建Jaeger开发环境(Docker 部署Jaeger all-in-one)
- 分布式调用链调研(pinpoint,skywalking,jaeger,zipkin等对比)
- Uber分布式追踪系统Jaeger使用介绍和案例
- skywalking-docker@docker hub
- elasticsearch-shanghai-zone@docker hub
- 在Jaeger和OpenTelemetry SDK混合环境中使用W3C Trace-Context
- 阿里云ARMS对OpenTelemetry Java SDK支持
- OpenTelemetryCollector-数据集散中心
- windows max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
- docker 安装 Skywalking
- opentelemetry-java-instrumentation翻译
- supported-libraries官网
- Exporting Open Telemetry Data to Jaeger
- opentelemetry-java-instrumentation