在现代的微服务架构中,一个用户请求可能需要跨多个服务才能完全处理。这样的复杂性可能会给诊断问题和性能优化带来挑战。因此,跨服务链路追踪技术的应用越来越受到关注。
今天,我们将以一个由 console,sso 和 privilege 三个服务组成的系统为例,探讨如何使用 OpenTelemetry 实现跨服务链路追踪。
在链路追踪中,我们关注的主要概念是 "Trace" 和 "Span"。Trace 是一个由多个 Span 组成的树状结构,代表一次完整的分布式请求。每个 Span 对应请求在一个服务中的处理过程。每个 Span 有一个唯一的 Span ID,所有 Span 共享一个 Trace ID。
当一个请求从一个服务传递到另一个服务时,Trace ID 和 Parent Span ID 会被包含在请求的元数据中(如 HTTP headers),这样接收请求的服务就知道新的 Span 属于哪个 Trace,以及它的父 Span 是谁。这样就实现了跨服务的请求追踪。
首先,我们需要在每个服务中初始化 OpenTelemetry。这通常在服务启动时完成。
以下是在 console 服务中初始化 OpenTelemetry 的示例:
package main
import (
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/semconv/v1.7.0"
)
func main() {
ctx := context.Background()
exporter, err := otlptrace.New(ctx,
otlptrace.WithInsecure(),
otlptrace.WithEndpoint("localhost:4317"),
)
if err != nil {
log.Fatalf("failed to create exporter: %v", err)
}
res := resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("console"),
)
bsp := sdktrace.NewBatchSpanProcessor(exporter)
tp := sdktrace.NewTracerProvider(
sdktrace.WithSpanProcessor(bsp),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
// Your application code here...
}
这段代码首先创建一个 OTLP exporter,然后创建一个新的 TracerProvider,并设置为全局的 TracerProvider。注意这里我们也设置了 TextMapPropagator 为 TraceContext,这是 W3C 的上下文传播标准。
sso 和 privilege 服务的初始化代码类似,只需要将 semconv.ServiceNameKey.String("console")
中的 "console" 替换为对应的服务名即可。
然后,我们需要在处理请求的每个函数中创建一个新的 Span,并将其传递给后续的处理函数或服务请求。这可以通过 context 完成。
以下是在 console 服务处理请求并调用 sso 服务的示例:
package main
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
func main() {
tracer := otel.Tracer("console")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handleRequest")
defer span.End()
// Call the sso service
req, _ := http.NewRequest("GET", "http://localhost:8000/sso", nil)
// Inject the context into the HTTP headers
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
http.DefaultClient.Do(req)
})
http.ListenAndServe(":8000", nil)
}
这段代码首先创建一个新的 Span,并将其添加到当前请求的 context 中。然后,当调用 sso 服务时,将当前 context 注入到新的 HTTP 请求的 headers 中。这样,sso 服务就能获取到包含 Trace ID 和 Parent Span ID 的 context,实现跨服务追踪。
在 sso 和 privilege 服务中的代码类似,只需替换对应的服务名和请求地址。
最后,我们可以使用 Jaeger 或其他追踪后端查看跨服务的请求链路。只要正确地配置了 OpenTelemetry 和 Jaeger,我们就可以在 web 界面上看到一个请求从发出到完成经过了哪些服务,每个服务处理请求的详细情况。
跨服务链路追踪是一个强大的工具,能够帮助我们理解和优化分布式系统。通过 OpenTelemetry,我们可以方便地在 Go 项目中实现跨服务链路追踪。希望这篇文章能帮助大家更好地理解和应用这个技术。