OpenTelemetry Tracing 的概念和一些语言的实现

可观测性 Observability

追踪为我们提供了当向应用程序发出请求时发生什么的大局观。无论您的应用程序是一个带有单一数据库的单体应用,还是一个由多个服务组成的复杂网络,追踪对于理解请求在您的应用程序中所走的完整“路径”至关重要。

span

试想一下,如果我们想要记录一个应用程序处理请求的完整路径,我们需要一些关键的数据点来确保能够准确地追踪和理解请求在系统中的流转。

为了记录请求处理的开始和结束时间,以测量请求在每个阶段的持续时间,所以我们至少需要两个时间戳。

为了在多个跨度之间关联和识别单个请求的完整路径,我们至少需要一个Id。

为了标识正在执行的具体操作或方法,便于在追踪中识别关键步骤,我们至少需要一个可读的名称。

可以在opentelemetry中看到对spans的定义。

  • Name
  • Parent span ID (empty for root spans)
  • Start and End Timestamps
  • Span Context (current spans)
  • Attributes
  • Span Events
  • Span Links
  • Span Status

其中前几个概念都可以涉及到了, 但是还有一些概念我们还没有涉及到.

如果一个跨度跟踪在电子商务系统中向用户购物车添加商品的操作,您可以捕获用户的ID、要添加到购物车的商品的ID以及购物车ID。您可以在创建跨度期间或之后向跨度添加属性Attributes。属性是键值对,包含元数据,您可以使用它们来注释跨度(Span),以携带有关其正在跟踪的操作的信息。

如果单个trace上下文中存在多个属性, 且各自都有意义没有办法, 在不同时间上维度也不相同, 此时可以考虑下 Span 事件,可以被看作是 Span 上的一个结构化日志消息(或注释),通常用于表示 Span 持续期间内有意义的、单一的时间点。

events vs attributes 官方会有一段介绍何时应该用events或是attributes, 我觉的有点生硬, 给出来他们的最终的输出效果更直观些.

// events vs attributes
{
	"attributes": {"K": "V"},
	"events ": {
		"ts": "1999-9-9 19:59:59", 
		"attributes": {"K": "V"}
	}
}

至此好像我们都在说同步操作内的追踪过程,如果我们有一个分布式系统,其中一些操作由追踪(Trace)来跟踪。作为对这些操作的响应,可能会排队执行一个额外的操作,但其执行是异步的。我们也可以用追踪来跟踪这个后续操作。

我们希望将后续操作的追踪与第一个追踪关联起来,但我们无法预测后续操作何时开始。我们需要将这两个追踪关联起来,所以我们将使用跨度链接。你可以将第一个追踪中的最后一个跨度链接到第二个追踪中的第一个跨度。现在,它们在因果上彼此关联。

kafka 是一种常见的异步场景,当生产者向Kafka主题发送消息时,创建一个新的Span,代表消息发送操作的开始。在这个Span中,可以添加属性,如消息大小、主题名称、分区信息等。当消费者从Kafka接收消息时,启动一个新的Span,记录消息拉取的开始。消费者可以在Span中添加属性,如消费者组ID、分区偏移量、消息键值等。这个Span可以通过Span链接(Span Link)与原始的消费者接收消息的Span关联起来,以表示因果关系。

Kafka生产者

producer, err := kafka.NewProducer(&kafka.ConfigMap{"bootstrap.servers": "localhost:9092"})
if err != nil {
    log.Fatal(err)
}
defer producer.Close()

// 创建一个span
ctx, span := otel.Tracer("producer").Start(context.Background(), "ProduceMessage")
defer span.End()

// 获取span的上下文
spanContext := span.SpanContext()
message := Message{
    Text:    "Hello",
    TraceID: spanContext.TraceID().String(),
    SpanID:  spanContext.SpanID().String(),
}

// 将消息编码为JSON
msgBytes, err := json.Marshal(message)
if err != nil {
    log.Fatalf("Failed to marshal message: %v", err)
}

// 发送消息
err = producer.Produce(&kafka.Message{
    TopicPartition: kafka.TopicPartition{Topic: &[]string{"my_topic"}[0], Partition: kafka.PartitionAny},
    Value:          msgBytes,
}, nil)

Kafka消费者

consumer, err := kafka.NewConsumer(&kafka.ConfigMap{
    "bootstrap.servers": "localhost:9092",
    "group.id":          "my_group",
    "auto.offset.reset": "earliest",
})
if err != nil {
    log.Fatal(err)
}
defer consumer.Close()

consumer.Subscribe("my_topic", nil)

for {
    msg, err := consumer.ReadMessage(-1)
    if err == nil {
        // 解析消息
        var message Message
        if err := json.Unmarshal(msg.Value, &message); err != nil {
            log.Printf("Failed to unmarshal message: %v\n", err)
            continue
        }

        // 创建一个span并链接到生产者的span
        ctx, span := otel.Tracer("consumer").Start(context.Background(), "ConsumeMessage",
            trace.WithLinks(trace.Link{
                SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
                    TraceID: trace.TraceIDFromHex(message.TraceID),
                    SpanID:  trace.SpanIDFromHex(message.SpanID),
                }),
            }),
        )
        defer span.End()

        log.Printf("Consumed message: %s\n", message.Text)
    } else {
        log.Printf("Error while consuming: %v\n", err)
    }
}

当然每个 Span 都有一个状态。可能的三个值是:

Unset(未设置)
Error(错误)
Ok(成功)
默认值是 Unset。一个状态为 Unset 的 Span 表示它跟踪的操作成功完成,没有发生错误。

当一个 Span 状态是 Error 时,这意味着它跟踪的操作中发生了某些错误。例如,这可能是因为服务器处理请求时返回了 HTTP 500 错误。

除了Span状态约定俗成,当创建一个 Span 时,它可以是以下几种类型之一:客户端(Client)、服务端(Server)、内部(Internal)、生产者(Producer)或消费者(Consumer)。这种 Span 类型为追踪后端提供了一个提示,说明应该如何组装追踪信息。根据 OpenTelemetry 规范,服务端 Span 的父 Span 通常是远程客户端 Span,客户端 Span 的子 Span 通常是服务端 Span。类似地,消费者 Span 的父 Span 总是生产者,生产者 Span 的子 Span 总是消费者。如果未提供,则假定 Span 类型为内部(Internal)。

至此Span中的全部概念已经讲清楚了。

  • Name
  • Parent span ID (empty for root spans)
  • Start and End Timestamps
  • Span Context (current spans)
  • Attributes
  • Span Events
  • Span Links
  • Span Status

net 追踪的特殊实现

之后我们会看一下asp .net core语言如何处理span,其实更好从go或者java开始看,dotnet会有一些历史的原因导致很多api是微软的诊断兼容层做的,点击图片可以看到这个图片的来源。

同时OpenTelemetry也对在基于 System.Diagnostics 的实现之上提供了一个 API Shim。如果您更倾向于使用与 OpenTelemetry 规范一致的术语,这个Shim会很有帮助。

实际使用中,如果你追求完美,这里有一套最佳实践可以遵循。

您可能已经注意到使用了 ActivitySource 和 Activity 这两个术语,而不是 OpenTelemetry 规范中的 Tracer 和 Span。这是因为 OpenTelemetry .NET 中的追踪实现有些独特,因为大部分追踪 API 实际上是由 .NET 运行时本身实现的。从高层次上看,这意味着您可以通过简单地依赖 System.Diagnostics.DiagnosticSource 包来对应用程序进行仪器化,该包提供了分别代表 OpenTelemetry 中 Span 和 Tracer 概念的 Activity 和 ActivitySource 类。

API
SDK
System.Diagnostics.Activity
System.Diagnostics.Activity
Batch
ActivitySource("MyCompany.MyProduct.MyLibrary")
TracerProvider
SimpleExportProcessor < Activity >
ConsoleExporter

后记

最后附上go和net语言对http server的追踪, 供大家探究使用.

net http 事件发布订阅实现

栈:

OpenTelemetry.Instrumentation.AspNetCore::AddAspNetCoreInstrumentation
OpenTelemetry.Instrumentation.AspNetCore::AspNetCoreInstrumentation
OpenTelemetry.Instrumentation.AspNetCore::HttpInListener
OpenTelemetry.Instrumentation.AspNet::AspNetInstrumentation
OpenTelemetry.Instrumentation.AspNetCore
diagnosticSourceSubscriber
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule::TelemetryHttpModule.Application_BeginRequest
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule::ActivityHelper.StartAspNetActivity
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule::AspNetSource.StartActivity

go http 中间件实现

栈:

otelhttp.NewHandler
otelhttp.NewMiddleware
otelhttp.h.serveHTTP
tracer.Start

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值