首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

微服务添加 OpenTelemetry (otel)

  • 25-04-18 12:40
  • 3831
  • 12212
juejin.cn

1. 定义全局 Tracer Provider

步骤描述:

• 初始化 OpenTelemetry 的 TracerProvider,并配置导出器(如 Jaeger、Zipkin、Prometheus 等)以收集和导出追踪数据。

代码示例:

go
代码解读
复制代码
import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/jaeger" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) func initTracer() { // 创建 Jaeger Exporter exporter, _ := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://jaeger:14268/api/traces"))) // 配置 TracerProvider tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), // 批量导出 Span sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("my-service"), // 服务名称 )), ) // 设置为全局 TracerProvider otel.SetTracerProvider(tp) }

补充说明:

  1. 资源属性(Resource Attributes): • 必须定义服务的元数据(如 service.name、service.version、deployment.environment),以便在链路追踪工具中区分不同服务和环境。 • 示例:

    go
    代码解读
    复制代码
    resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("order-service"), semconv.ServiceVersionKey.String("v1.0.0"), semconv.DeploymentEnvironmentKey.String("production"), )
  2. 采样策略: • 配置采样率以平衡性能和数据完整性。 • 示例:

    go
    代码解读
    复制代码
    sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)) // 采样 10% 的请求
  3. 多环境支持: • 根据环境(开发、测试、生产)动态配置导出器和采样策略。


2. 添加 otelgin 和 otelhttp 中间件

步骤描述:

• 使用 OpenTelemetry 的 Gin 或 HTTP 中间件自动为每个请求创建根 Span,并记录 HTTP 相关的元数据(如方法、路径、状态码)。

代码示例(Gin):

go
代码解读
复制代码
import ( "github.com/gin-gonic/gin" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) func main() { r := gin.Default() // 添加 OpenTelemetry 中间件 r.Use(otelgin.Middleware("order-service")) // 服务名称需与 TracerProvider 配置一致 r.GET("/api/orders", func(c *gin.Context) { c.JSON(200, gin.H{"message": "Hello, orders!"}) }) r.Run(":8080") }

代码示例(HTTP):

go
代码解读
复制代码
import ( "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/api/orders", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, orders!")) }) // 包装 HTTP 处理器 handler := otelhttp.NewHandler(http.DefaultServeMux, "order-service") http.ListenAndServe(":8080", handler) }

补充说明:

  1. 服务名称一致性: • 中间件中的服务名称(如 "order-service")必须与 TracerProvider 中配置的 service.name 一致,以确保链路追踪工具正确识别服务。

  2. 上下文传递: • 中间件会自动将 Trace 上下文注入到 HTTP 请求头中(如 traceparent),确保跨服务调用时上下文连续。

  3. 自定义属性: • 可在中间件中添加自定义属性(如用户 ID、请求 ID):

    go
    代码解读
    复制代码
    r.Use(func(c *gin.Context) { ctx := metadata.AppendToOutgoingContext(c.Request.Context(), "user-id", "123") c.Request = c.Request.WithContext(ctx) c.Next() })
  4. 日志集成: • 结合 OpenTelemetry Logs SDK,将日志与 Trace 关联,提升可观测性。

  5. grpc可通过服务端,客户端自动添加otel遥测

css
代码解读
复制代码
#服务端添加 otelHandler := otelgrpc.NewServerHandler() grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( orderService.AuthMiddleware, ), grpc.StatsHandler(otelHandler), ) #客户端添加 conn, err := grpc.NewClient( config.AuthService.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()), //添加OpenTelemetry遥测 grpc.WithStatsHandler(otelgrpc.NewClientHandler()), )
  1. 在 gRPC 中,我们用 grpc.WithStatsHandler(otelgrpc.NewClientHandler()) 自动将上下文信息(TraceContext)传递到下游服务。而在 HTTP/Gin 作为客户端 的场景下,OpenTelemetry 没有一个“自动注入中间件” ,但我们可以用一个官方推荐的方式来处理:手动使用 otelhttp 封装 http.Client 请求,从而自动注入 trace 信息。

Gin 中作为 HTTP 客户端调用其他服务时的做法:

使用 otelhttp 封装 http.Client:

go
代码解读
复制代码
import ( "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) var client = http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport), }

这样创建出来的 client,在发请求时就会自动把 trace context 写入请求头中(也就是实现了 trace 的传播)。


🧪 示例:调用其他 Gin HTTP 接口

go
代码解读
复制代码
req, _ := http.NewRequestWithContext(ctx, "GET", "http://inventory-service:8080/api/v1/product/123", nil) res, err := client.Do(req)

注意:

  • ctx 应该是从当前的 gin 请求上下文中提取出来的,例如:

    css
    代码解读
    复制代码
    ctx := c.Request.Context()
  • 上面的 client.Do(req) 会自动将 trace ID、span ID、baggage 等信息注入请求头中,如:traceparent

  1. 通过gin接口调用grpc方法,自动传递otel

背景:什么是“同一个 ctx”?

在 Go 中,context.Context 是包含 请求范围元数据(metadata) 的载体。OpenTelemetry 把 trace 信息(TraceID、SpanID 等)嵌入在这个 ctx 中,当你用这个 ctx 去调用下游服务时,otel 会自动把 trace metadata 注入到请求头里(HTTP 或 gRPC metadata)。


示例:Gin + gRPC 客户端 调用 inventory 服务

  1. Gin HTTP handler(order service)
go
代码解读
复制代码
// handler/order_handler.go func (h *OrderHandler) CreateOrder(c *gin.Context) { // 从 gin 的请求中获取 context(包含 trace 信息) ctx := c.Request.Context() // 使用这个 ctx 作为 gRPC 调用的 context resp, err := h.InventoryClient.ReserveStock(ctx, &inventorypb.ReserveStockRequest{ ProductId: "p1", Quantity: 1, }) if err != nil || !resp.Success { c.JSON(http.StatusInternalServerError, gin.H{"error": "库存预扣失败"}) return } // 继续处理... c.JSON(http.StatusOK, gin.H{"message": "订单创建成功"}) }

关键点:

  • 使用 ctx := c.Request.Context() 保留了 trace 上下文
  • 把这个 ctx 原封不动传给下游 gRPC 客户端

  1. gRPC 客户端初始化(order service)
go
代码解读
复制代码
// internal/client/inventory_client.go func InitInventoryClient() (inventorypb.InventoryServiceClient, error) { conn, err := grpc.NewClient( "inventory-service:50052", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(otelgrpc.NewClientHandler()), // 自动从 ctx 中提取 trace 信息 ) if err != nil { return nil, err } return inventorypb.NewInventoryServiceClient(conn), nil }

  1. gRPC 服务端(inventory service)
css
代码解读
复制代码
// main.go grpcServer := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler()), // 自动解析 trace 信息 ) inventorypb.RegisterInventoryServiceServer(grpcServer, inventoryServer)

链路完整图(Jaeger中可视化)

scss
代码解读
复制代码
[POST /create-order] ↓ gin middleware (otelgin) ↓ ctx 含 trace 信息 [gRPC call → inventory] ↓ otelgrpc client handler ↓ metadata 写入 trace [Inventory gRPC 服务端] ↓ otelgrpc server handler ↓ trace 继续传递

总结

步骤动作保证了什么
ctx := c.Request.Context()从 HTTP 获取 trace 信息保证 trace 上下文起点
grpcClient.Call(ctx, ...)将 trace 信息传递给 gRPC保证 trace 传播
otelgrpc.NewClientHandler()自动打包 trace 到 metadata无需手动写入 trace ID
otelgrpc.NewServerHandler()自动解包 metadata → ctx服务端继续追踪

3. 在服务调用其他服务时通过 otel.Tracer.Start 创建子 Span

步骤描述:

• 在跨服务调用时,手动创建子 Span,并将 Trace 上下文注入到请求头中,确保调用链的连续性。

代码示例(gRPC):

go
代码解读
复制代码
func (s *OrderService) CallAnotherService(ctx context.Context, req *pb.Request) (*pb.Response, error) { // 创建子 Span ctx, span := otel.Tracer("call-another-service").Start( ctx, "call-another-service", trace.WithSpanKind(trace.SpanKindClient), // 标记为客户端调用 ) defer span.End() // 将 Trace 上下文注入到 gRPC 元数据中 md, _ := metadata.FromOutgoingContext(ctx) ctx = metadata.NewOutgoingContext(ctx, metadata.Join(md, propagation.HeaderCarrier(propagator))) // 调用远程服务 client := pb.NewAnotherServiceClient(s.conn) return client.AnotherMethod(ctx, req) }

代码示例(HTTP):

go
代码解读
复制代码
func CallExternalAPI(ctx context.Context, url string) (*http.Response, error) { // 创建子 Span ctx, span := otel.Tracer("call-external-api").Start( ctx, "call-external-api", trace.WithSpanKind(trace.SpanKindClient), ) defer span.End() // 将 Trace 上下文注入到 HTTP 请求头中 req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) // 发送请求 client := &http.Client{} return client.Do(req) }

补充说明:

  1. Span 类型: • 使用 trace.SpanKindClient 标记客户端调用,确保链路追踪工具正确识别调用方向。

  2. 上下文注入: • 使用 OpenTelemetry 的 propagator 将 Trace 上下文注入到请求头中(如 gRPC 的 metadata 或 HTTP 的 Header)。 • 示例(HTTP):

    go
    代码解读
    复制代码
    propagator.Inject(ctx, propagation.HeaderCarrier(req.Header))
  3. 跨语言支持: • 确保所有服务使用相同的 Propagator(如 W3C TraceContext),以实现跨语言链路追踪。

  4. 错误处理: • 在子 Span 中记录错误状态和耗时,便于排查问题。


4. 补充步骤和建议

虽然上述步骤已覆盖核心流程,但仍有一些补充步骤和优化建议:

(1) 初始化 Propagator

• 确保正确配置上下文传播器(Propagator),以支持跨服务调用时的 Trace 上下文传递。 • 示例:

go
代码解读
复制代码
import "go.opentelemetry.io/otel/propagation" func init() { propagator := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, // W3C TraceContext propagation.Baggage{}, // Baggage ) otel.SetTextMapPropagator(propagator) }

(2) 添加 Metrics 和 Logs

• Metrics: • 使用 OpenTelemetry Metrics SDK 收集服务性能指标(如请求延迟、错误率)。 • 示例: ```go import "go.opentelemetry.io/otel/metric"

css
代码解读
复制代码
meter := otel.Meter("order-service") requestCounter := meter.Int64Counter("requests_total") requestCounter.Add(ctx, 1) ```

• Logs: • 使用 OpenTelemetry Logs SDK 记录结构化日志,并与 Trace 关联。 • 示例: ```go import "go.opentelemetry.io/otel/sdk/log"

css
代码解读
复制代码
logger := log.NewLogger(log.WithSink(os.Stdout)) logger.Log(context.Background(), "info", "request received") ```

(3) 配置 Exporter

• 根据需求选择合适的 Exporter(如 Jaeger、Zipkin、Prometheus、OTLP)。 • 示例(OTLP):

go
代码解读
复制代码
exporter, _ := otlp.NewExporter(otlp.WithInsecure()) tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter))

(4) 动态配置

• 使用环境变量或配置文件动态设置服务名称、采样率和导出器。 • 示例:

go
代码解读
复制代码
serviceName := os.Getenv("SERVICE_NAME") if serviceName == "" { serviceName = "default-service" }

(5) 测试和验证

• 使用链路追踪工具(如 Jaeger、Zipkin)验证 Span 是否正确生成和传递。 • 检查 Trace 的完整性(如服务间调用链是否连续)。


完整流程总结

步骤描述工具/组件
1. 初始化 Tracer Provider配置全局 Tracer Provider 和 Exporter,定义资源属性和采样策略。sdktrace.NewTracerProvider
2. 添加中间件使用 otelgin.Middleware 或 otelhttp 自动创建根 Span,记录 HTTP 元数据。otelgin 或 otelhttp
3. 创建子 Span在跨服务调用时手动创建子 Span,并注入 Trace 上下文。otel.Tracer.Start
4. 配置 Propagator确保 Trace 上下文在服务间正确传递。propagation.NewCompositeTextMapPropagator
5. 添加 Metrics 和 Logs收集性能指标和结构化日志,增强可观测性。otel.Meter, otel/sdk/log
6. 动态配置和测试根据环境动态配置 Tracer,并验证链路追踪数据的完整性。环境变量、测试工具

最终建议

• 核心流程:上述步骤已覆盖单个服务添加 OpenTelemetry 的主要流程。 • 补充优化:根据实际需求,添加 Propagator 配置、Metrics、Logs 和动态配置功能,进一步提升可观测性。 • 验证和监控:通过链路追踪工具(如 Jaeger)和监控系统(如 Prometheus)验证和优化链路追踪数据。

通过以上补充,可以确保单个服务的 OpenTelemetry 实现更加完整和健壮。

注:本文转载自juejin.cn的forever23的文章"https://juejin.cn/post/7493749701780193334"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

142
代码人生
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top