同一进程多Web应用运行时OpenTelemetry Span ID重复问题解决
同一进程多WebApplication的OpenTelemetry追踪隔离方案
问题背景
同一进程内运行两个监听不同端口的独立WebApplication(服务名分别为GATEWAY和SUBSCRIPTIONS),各自配置OpenTelemetry时使用了以下代码:
builder.Services.AddOpenTelemetry() .WithTracing(tracerProviderBuilder => tracerProviderBuilder .ConfigureResource(cfg => { cfg.AddService(serviceName); }) .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddGrpcClientInstrumentation() .AddSource("GraphQL") .AddSource(GraphQLTelemetryProvider.SourceName) .AddSource(DiagnosticHeaders.DefaultListenerName) .AddSentry() .AddOtlpExporter(options => { options.Endpoint = new Uri("http://localhost:4317"); }) );
实际运行出现以下问题:
- Jaeger中追踪数据重复:GATEWAY收到请求后发起的外部请求,Jaeger同时显示GATEWAY和SUBSCRIPTIONS的入站请求,且出站请求全部归属GATEWAY
- 日志中大量出现
duplicate span IDs; skipping clock skew adjustment警告,不同服务的Span被重复记录
根本原因
同一进程内默认共享了OpenTelemetry的TracerProvider和DiagnosticListener实例,导致两个WebApplication的 instrumentation 会监听相同的诊断事件,进而重复采集Span,引发Span ID冲突。
解决方案
为每个WebApplication创建完全独立的OpenTelemetry配置,包括独立的服务集合、TracerProvider和Instrumentation实例,避免资源共享。
1. 拆分服务配置,独立构建WebApplication
不要在共享容器中配置OTEL,分别为两个服务创建专属的WebApplicationBuilder,各自配置OTEL:
GATEWAY服务配置示例
// 为GATEWAY创建独立的构建器 var gatewayBuilder = WebApplication.CreateBuilder(args); gatewayBuilder.Services.AddOpenTelemetry() .WithTracing(tracerProviderBuilder => tracerProviderBuilder .ConfigureResource(cfg => cfg.AddService("GATEWAY")) // 关键:通过端口筛选只追踪GATEWAY的请求 .AddAspNetCoreInstrumentation(options => { options.Filter = context => context.Request.Host.Port == 5000; // 替换为GATEWAY实际监听端口 }) .AddHttpClientInstrumentation() .AddGrpcClientInstrumentation() .AddSource("GraphQL") .AddSource(GraphQLTelemetryProvider.SourceName) .AddSource(DiagnosticHeaders.DefaultListenerName) .AddSentry() .AddOtlpExporter(options => { options.Endpoint = new Uri("http://localhost:4317"); }) ); // 配置GATEWAY的路由、中间件等逻辑 var gatewayApp = gatewayBuilder.Build(); // ... 此处添加GATEWAY的业务配置 gatewayApp.Run("http://localhost:5000");
SUBSCRIPTIONS服务配置示例
// 为SUBSCRIPTIONS创建独立的构建器 var subscriptionsBuilder = WebApplication.CreateBuilder(args); subscriptionsBuilder.Services.AddOpenTelemetry() .WithTracing(tracerProviderBuilder => tracerProviderBuilder .ConfigureResource(cfg => cfg.AddService("SUBSCRIPTIONS")) // 关键:通过端口筛选只追踪SUBSCRIPTIONS的请求 .AddAspNetCoreInstrumentation(options => { options.Filter = context => context.Request.Host.Port == 5001; // 替换为SUBSCRIPTIONS实际监听端口 }) .AddHttpClientInstrumentation() .AddGrpcClientInstrumentation() .AddSource("GraphQL") .AddSource(GraphQLTelemetryProvider.SourceName) .AddSource(DiagnosticHeaders.DefaultListenerName) .AddSentry() .AddOtlpExporter(options => { options.Endpoint = new Uri("http://localhost:4317"); }) ); // 配置SUBSCRIPTIONS的路由、中间件等逻辑 var subscriptionsApp = subscriptionsBuilder.Build(); // ... 此处添加SUBSCRIPTIONS的业务配置 subscriptionsApp.Run("http://localhost:5001");
2. 额外优化(可选)
如果仍存在跨服务事件监听问题,可以:
- 为每个服务添加唯一的自定义SourceName,避免共享Source导致的事件交叉
- 对HttpClient/GrpcClient Instrumentation也添加筛选逻辑,确保出站请求归属正确服务
3. 验证效果
启动两个服务后,分别发起请求,检查Jaeger:
- 各服务的请求只显示对应服务名的Span
- 不再出现
duplicate span IDs警告
核心总结
- 必须为每个WebApplication独立配置OpenTelemetry,禁止共享服务容器中的OTEL资源
- 通过AspNetCore Instrumentation的Filter选项精准过滤当前服务的请求,防止跨服务采集
- 确保每个服务拥有独立的TracerProvider实例,从根源上隔离追踪数据
内容的提问来源于stack exchange,提问作者user606521
相关产品推荐
相关产品推荐

