OpenTelemetry与Jaeger集成指南(大部分内容由 gpt 生成)
本文档介绍了如何在前端项目中使用OpenTelemetry进行分布式追踪,并将追踪数据发送到Jaeger服务以进行监控和分析。
目录
- OpenTelemetry简介
- 在前端项目中使用OpenTelemetry
- 创建自定义ClickHouse导出器
- 在Grafana中展示ClickHouse数据
- Jaeger简介
- 部署Jaeger服务
OpenTelemetry简介
OpenTelemetry是一个开源项目,其目标是为分布式系统提供一套统一的、可靠的和可扩展的跟踪和度量数据收集工具。它是由OpenTracing和OpenCensus两个项目合并而成的,现在由云原生计算基金会(CNCF)进行管理和维护。
OpenTelemetry提供了一套API、库和工具,使开发人员能够方便地为他们的应用程序和服务添加跟踪、度量和日志记录功能。这使得在整个系统中可以更容易地收集和分析性能数据,有助于监控、故障排查和优化分布式系统。
核心概念
OpenTelemetry具有一些核心概念,这些概念有助于理解和使用该框架进行分布式追踪和度量数据收集。以下是一些主要概念:
-
跟踪(Tracing):跟踪是一种记录分布式系统中请求路径和延迟的技术。通过收集和分析跟踪数据,可以监控系统性能、发现瓶颈并优化服务。
-
跨度(Span):跨度是一段连续的操作,表示分布式追踪中的一个工作单元。每个跨度包含一个操作名称、开始时间、结束时间、以及其他可选元数据。跨度可以嵌套,并形成一个跨度树,以表示请求在系统中的完整执行路径。
-
追踪上下文(Trace Context):追踪上下文包含了跟踪过程中的元数据,例如追踪ID(Trace ID)和跨度ID(Span ID)。追踪上下文在服务之间传递,以确保完整的请求路径可以在分布式系统中进行跟踪。
-
度量(Metrics):度量是对系统性能的定量测量,例如请求速率、错误计数、资源利用率等。OpenTelemetry提供了收集和导出度量数据的工具,以便在监控系统中进行分析。
-
资源(Resource):资源是一个表示可观测系统实体(如主机、容器、服务等)的对象。资源可以附加到跟踪和度量数据上,以提供有关数据来源的上下文信息。
-
导出器(Exporter):导出器负责将收集到的跟踪和度量数据发送到后端监控系统。OpenTelemetry支持多种导出器,可以将数据发送到不同的监控和分析工具,如Jaeger、Zipkin、Prometheus等。
-
处理器(Processor):处理器用于在导出跟踪数据之前处理和过滤数据。例如,可以使用处理器对跨度进行采样、聚合或丢弃。
-
提供者(Provider):提供者负责创建和管理追踪器(Tracer)和度量仪(Meter)实例。提供者还负责将处理器和导出器与OpenTelemetry SDK集成。
了解这些核心概念有助于您更好地使用OpenTelemetry来监控和优化您的分布式系统。
这是一个更详细的图示,展示了OpenTelemetry核心概念、组件及其关系:
+----------------+ +-----------+ +----------+ +----------+| | | | | | | || Application +-----> Tracer +-----> Spans +-----> Processor|| | | | | | | |+----------------+ +-----------+ +----------+ +-----+----+|v+----------------+ +-----------+ +----------+ +-------------+| | | | | | | || Application +-----> Meter +-----> Metrics +-----> Exporter || | | | | | | |+----------------+ +-----------+ +----------+ +-------------+|v+-------+------+| || Backend || Monitoring || System || |+--------------++----------------+ +-----------+ +----------+ +----------+ | | | | | | | | | Application +-----> Tracer +-----> Spans +-----> Processor| | | | | | | | | +----------------+ +-----------+ +----------+ +-----+----+ | v +----------------+ +-----------+ +----------+ +-------------+ | | | | | | | | | Application +-----> Meter +-----> Metrics +-----> Exporter | | | | | | | | | +----------------+ +-----------+ +----------+ +-------------+ | v +-------+------+ | | | Backend | | Monitoring | | System | | | +--------------++----------------+ +-----------+ +----------+ +----------+ | | | | | | | | | Application +-----> Tracer +-----> Spans +-----> Processor| | | | | | | | | +----------------+ +-----------+ +----------+ +-----+----+ | v +----------------+ +-----------+ +----------+ +-------------+ | | | | | | | | | Application +-----> Meter +-----> Metrics +-----> Exporter | | | | | | | | | +----------------+ +-----------+ +----------+ +-------------+ | v +-------+------+ | | | Backend | | Monitoring | | System | | | +--------------+
- Application:这是您的应用程序或服务,它使用OpenTelemetry库进行跟踪和度量数据收集。
- Tracer:Tracer负责在应用程序中创建和管理跨度。它通常与提供者关联,以便在需要时创建Tracer实例。
- Spans:Spans是跟踪过程中的工作单元,表示一个操作或事件。Spans可以嵌套,并形成一个跨度树,表示请求在分布式系统中的执行路径。
- Processor:处理器用于在导出跟踪数据之前处理和过滤数据。例如,可以使用处理器对跨度进行采样、聚合或丢弃。
- Meter:Meter负责在应用程序中创建和管理度量数据。它通常与提供者关联,以便在需要时创建Meter实例。
- Metrics:这些是收集到的度量数据,例如计数器、计时器、值记录器等。度量数据可用于监控系统性能和资源利用率。
- Exporter:Exporter负责将收集到的跨度和度量数据发送到后端监控系统。可以配置不同的导出器,将数据发送到不同的监控和分析工具。
- Backend Monitoring System:这是一个后端监控系统,如Jaeger、Prometheus或Grafana,用于存储、分析和可视化收集到的跟踪和度量数据。
这个图示展示了应用程序如何使用Tracer和Meter来收集跨度和度量数据,然后通过Processor和Exporter发送到后端监控系统。这些核心概念共同构成了OpenTelemetry框架的基础。
在前端项目中使用OpenTelemetry
安装依赖
使用npm或yarn安装所需的依赖包:
npm install --save @opentelemetry/apinpm install --save @opentelemetry/corenpm install --save @opentelemetry/webnpm install --save @opentelemetry/tracingnpm install --save @opentelemetry/exporter-jaegernpm install --save @opentelemetry/exporter-zipkinnpm install --save @opentelemetry/api npm install --save @opentelemetry/core npm install --save @opentelemetry/web npm install --save @opentelemetry/tracing npm install --save @opentelemetry/exporter-jaeger npm install --save @opentelemetry/exporter-zipkinnpm install --save @opentelemetry/api npm install --save @opentelemetry/core npm install --save @opentelemetry/web npm install --save @opentelemetry/tracing npm install --save @opentelemetry/exporter-jaeger npm install --save @opentelemetry/exporter-zipkin
配置OpenTelemetry
在您的前端项目中创建一个文件(例如:tracing.ts
),并添加以下内容:
import { ConsoleSpanExporter, SimpleSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web';import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';import { Resource } from "@opentelemetry/resources";import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";const resource = Resource.default().merge(new Resource({[SemanticResourceAttributes.SERVICE_NAME]: "service-name-here",[SemanticResourceAttributes.SERVICE_VERSION]: "0.1.0",}));// 初始化WebTracerProviderconst provider = new WebTracerProvider({resource,});provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));// 配置 SimpleSpanProcessor 使用指定的导出器provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter()));// 注册 provider// provider.register({// contextManager: new ZoneContextManager(),// propagator: new B3Propagator(),// });provider.register();import { ConsoleSpanExporter, SimpleSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; const resource = Resource.default().merge(new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "service-name-here", [SemanticResourceAttributes.SERVICE_VERSION]: "0.1.0", })); // 初始化WebTracerProvider const provider = new WebTracerProvider({ resource, }); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); // 配置 SimpleSpanProcessor 使用指定的导出器 provider.addSpanProcessor( new SimpleSpanProcessor(new OTLPTraceExporter()) ); // 注册 provider // provider.register({ // contextManager: new ZoneContextManager(), // propagator: new B3Propagator(), // }); provider.register();import { ConsoleSpanExporter, SimpleSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; const resource = Resource.default().merge(new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "service-name-here", [SemanticResourceAttributes.SERVICE_VERSION]: "0.1.0", })); // 初始化WebTracerProvider const provider = new WebTracerProvider({ resource, }); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); // 配置 SimpleSpanProcessor 使用指定的导出器 provider.addSpanProcessor( new SimpleSpanProcessor(new OTLPTraceExporter()) ); // 注册 provider // provider.register({ // contextManager: new ZoneContextManager(), // propagator: new B3Propagator(), // }); provider.register();
使用装饰器封装OpenTelemetry
创建一个装饰器文件(例如:trace-decorator.ts
):
import { trace, Tracer, Span, context } from '@opentelemetry/api';export function Trace() {const tracer: Tracer = trace.getTracer('your-frontend-service-name');return function (target: Object,propertyKey: string,descriptor: PropertyDescriptor) {const originalMethod = descriptor.value;descriptor.value = function (...args: any[]) {const span: Span = tracer.startSpan(`${target.constructor.name}.${propertyKey}`);const result = context.with(trace.setSpan(context.active(), span), () => {try {return originalMethod.apply(this, args);} catch (error: any) {span.recordException(error);span.setStatus({ code: 2, message: error.message });throw error;} finally {span.end();}});return result;};return descriptor;};}import { trace, Tracer, Span, context } from '@opentelemetry/api'; export function Trace() { const tracer: Tracer = trace.getTracer('your-frontend-service-name'); return function ( target: Object, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { const span: Span = tracer.startSpan(`${target.constructor.name}.${propertyKey}`); const result = context.with(trace.setSpan(context.active(), span), () => { try { return originalMethod.apply(this, args); } catch (error: any) { span.recordException(error); span.setStatus({ code: 2, message: error.message }); throw error; } finally { span.end(); } }); return result; }; return descriptor; }; }import { trace, Tracer, Span, context } from '@opentelemetry/api'; export function Trace() { const tracer: Tracer = trace.getTracer('your-frontend-service-name'); return function ( target: Object, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { const span: Span = tracer.startSpan(`${target.constructor.name}.${propertyKey}`); const result = context.with(trace.setSpan(context.active(), span), () => { try { return originalMethod.apply(this, args); } catch (error: any) { span.recordException(error); span.setStatus({ code: 2, message: error.message }); throw error; } finally { span.end(); } }); return result; }; return descriptor; }; }
在您的类和方法上应用装饰器:
index.ts
import { Trace } from './trace-decorator';class ExampleClass {@Trace()public topLevelMethod(): void {console.log('Executing top-level method');this.nestedMethod();}@Trace()public nestedMethod(): void {console.log('Executing nested method');}}const exampleInstance = new ExampleClass();exampleInstance.topLevelMethod();import { Trace } from './trace-decorator'; class ExampleClass { @Trace() public topLevelMethod(): void { console.log('Executing top-level method'); this.nestedMethod(); } @Trace() public nestedMethod(): void { console.log('Executing nested method'); } } const exampleInstance = new ExampleClass(); exampleInstance.topLevelMethod();import { Trace } from './trace-decorator'; class ExampleClass { @Trace() public topLevelMethod(): void { console.log('Executing top-level method'); this.nestedMethod(); } @Trace() public nestedMethod(): void { console.log('Executing nested method'); } } const exampleInstance = new ExampleClass(); exampleInstance.topLevelMethod();
Jaeger简介
Jaeger是一个开源的分布式追踪系统,用于监控、分析和调试微服务架构中的事务流程和性能。Jaeger由Uber开发,后来成为了云原生计算基金会(CNCF)的项目。Jaeger提供了端到端的请求追踪功能,帮助开发人员识别性能瓶颈、延迟问题、服务依赖关系等,从而优化分布式系统的性能。
部署Jaeger服务
部署Jaeger服务有多种方法,以下是使用Docker的快速部署方法:
-
确保您已经安装了Docker。
-
从Docker Hub获取Jaeger的all-in-one镜像。这个镜像包含了Jaeger的所有组件(Agent、Collector、Query和UI),并使用内存作为存储后端。这种部署方式非常适合本地开发和测试:
docker pull jaegertracing/all-in-onedocker pull jaegertracing/all-in-onedocker pull jaegertracing/all-in-one
- 运行Jaeger all-in-one容器:
docker run -d --name jaeger \-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 14250:14250 \-p 9411:9411 \jaegertracing/all-in-one:latestdocker run -d --name jaeger \ -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 14250:14250 \ -p 9411:9411 \ jaegertracing/all-in-one:latestdocker run -d --name jaeger \ -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 14250:14250 \ -p 9411:9411 \ jaegertracing/all-in-one:latest
这将启动一个名为“jaeger”的容器,并将Jaeger的各个组件端口映射到宿主机。现在,您可以通过访问http://localhost:16686
来使用Jaeger UI。
请注意,这种部署方式主要用于本地开发和测试。对于生产环境,您需要考虑使用更可扩展的部署方式,例如将Jaeger组件分别部署,并使用外部存储后端(如Cassandra或Elasticsearch)。
有关Jaeger部署的更多详细信息和选项,请参阅Jaeger官方文档。
构建和运行项目
使用 tsc 编译 index.ts 文件,然后执行
node dist/index.jsnode dist/index.jsnode dist/index.js
执行完之后, 打开 http://localhost:16686 页面,就可以看到有数据上报了。