ABP VNext + OpenTelemetry + Jaeger:分布式追蹤與調用鏈可視化 🚀
📚 目錄
- ABP VNext + OpenTelemetry + Jaeger:分布式追蹤與調用鏈可視化 🚀
- 背景與動機 🌟
- 環境與依賴 📦
- 必裝 NuGet 包
- 系統架構概覽 🖥?
- 跑通示例 🚀
- `Program.cs`
- `docker-compose.yml` 🐳
- 自動 + 手動埋點 🔍
- 自動埋點
- 自定義業務 Span
- 異常與狀態碼標記 ??
- 日志與 Metrics 關聯 📑
- Jaeger 部署與高可用 🏗?
- 采樣策略與性能優化 🛡?
- 擴展:OTel Collector & Grafana Tempo 🔗
版本說明:本文基于
OpenTelemetry.Extensions.Hosting
>=1.4.0 編寫,推薦使用統一的.AddOpenTelemetry().WithTracing(...).WithMetrics(...)
API。
TL;DR
- 提供完整
Program.cs
+docker-compose.yml
示例 🏃?♂?- 自動 + 手動埋點:支持 HTTP/gRPC、數據庫、外部調用與自定義 Span 🔍
- 高性能采樣:Parent-Based + TraceIdRatioBasedSampler,動態可配 ??
- 生產級部署:Batch 模式、OTel Collector、日志/Metrics 關聯、異常標記 🚀
背景與動機 🌟
在分布式微服務架構中,調用鏈橫跨多個進程與網絡節點,“誰調用了誰”、“哪些環節慢”成為痛點。
OpenTelemetry(OTel)與 Jaeger 提供了開源、無侵入的端到端分布式追蹤解決方案,幫助我們:
- 自動化采集:入站 HTTP/gRPC、數據庫、外部 HTTP 等一鍵埋點 📡
- 自定義業務 Span:靈活埋點關鍵業務邏輯 🛠?
- 統一可視化:Jaeger UI 或 Grafana Tempo 展示完整調用鏈 📈
本文基于 ABP VNext 6.x + .NET 6+,演示從零搭建到生產級優化,涵蓋自動/手動埋點、采樣策略、異常與日志關聯等最佳實踐。
環境與依賴 📦
- .NET SDK:6.0+
- ABP Framework:vNext 6.x
- OpenTelemetry.Extensions.Hosting:>=1.4.0
- Jaeger:all-in-one(測試);獨立 Agent/Collector/Storage(生產)
- 可選:OpenTelemetry Collector、Grafana Tempo、Prometheus/Grafana
必裝 NuGet 包
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Instrumentation.SqlClient
dotnet add package OpenTelemetry.Instrumentation.GrpcNetClient
dotnet add package OpenTelemetry.Instrumentation.GrpcAspNetCore
dotnet add package OpenTelemetry.Exporter.Jaeger
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
系統架構概覽 🖥?
- 采集:入站 HTTP/gRPC、EF Core/SqlClient、HttpClient、gRPC 客戶端、日志、Metrics
- 導出:Trace → Jaeger;Metrics → Prometheus;Logs → OTLP → 日志后端;(可選)Trace → Tempo
跑通示例 🚀
Program.cs
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Volo.Abp;var builder = WebApplication.CreateBuilder(args);// 1. ABP 模塊注冊
builder.Services.AddApplication<MyProjectHttpApiHostModule>();// 2. OpenTelemetry 注冊(Tracing + Metrics)
builder.Services.AddOpenTelemetry()// Tracing.WithTracing(tracing => tracing.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OrderService", serviceVersion: "1.0.0")).AddAspNetCoreInstrumentation().AddHttpClientInstrumentation().AddSqlClientInstrumentation().AddGrpcAspNetCoreInstrumentation().AddGrpcClientInstrumentation().AddSource("MyCompany.MyProduct").AddJaegerExporter(opts =>{opts.AgentHost = builder.Configuration["Jaeger:Host"];opts.AgentPort = int.Parse(builder.Configuration["Jaeger:Port"]!);}, exportProcessorType: ExportProcessorType.Batch).SetSampler(new ParentBasedSampler(new TraceIdRatioBasedSampler(0.1))))// Metrics.WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation().AddPrometheusExporter());// 3. 日志關聯 TraceContext
builder.Logging.AddOpenTelemetry(logging =>
{logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OrderService"));logging.IncludeFormattedMessage = true;logging.IncludeScopes = true;logging.ParseStateValues = true;logging.AddOtlpExporter(); // 需要 OpenTelemetry.Exporter.OpenTelemetryProtocol
});var app = builder.Build();// 4. Prometheus 默認抓取端點(/metrics),無需額外配置
app.MapPrometheusScrapingEndpoint();// 5. 啟動 ABP 應用
app.InitializeApplication();
app.Run();
docker-compose.yml
🐳
version: '3.8'
services:jaeger:image: jaegertracing/all-in-one:1.45ports:- "6831:6831/udp"- "16686:16686"- "14250:14250"
快速啟動:
docker-compose up -d
dotnet run --project src/MyProject.HttpApi.Host
- 訪問 API &
http://localhost:16686
自動 + 手動埋點 🔍
自動埋點
- HTTP/gRPC:
.AddAspNetCoreInstrumentation()
、.AddGrpcAspNetCoreInstrumentation()
- 外部調用:
.AddHttpClientInstrumentation()
、.AddGrpcClientInstrumentation()
- 數據庫:
.AddSqlClientInstrumentation()
自定義業務 Span
public class OrderAppService : ApplicationService
{private static readonly ActivitySource Source = new("MyCompany.MyProduct");public async Task ProcessOrderAsync(Guid orderId){using var activity = Source.StartActivity("ProcessOrder");activity?.SetTag("order.id", orderId);try{await _orderManager.HandleOrderAsync(orderId);}catch (Exception ex){activity?.SetStatus(ActivityStatusCode.Error, ex.Message);activity?.RecordException(ex);throw;}}
}
💡 Tip:通過 ABP 攔截器統一埋點:
public class TraceInterceptor : IInterceptor
{private static readonly ActivitySource Source = new("MyCompany.MyProduct");public void Intercept(IInvocation invocation){using var activity = Source.StartActivity(invocation.Method.Name);activity?.SetTag("abp.service", invocation.TargetType.Name);try{invocation.Proceed();}catch (Exception ex){activity?.SetStatus(ActivityStatusCode.Error, ex.Message);activity?.RecordException(ex);throw;}}
}// 注冊攔截器
Configure<AbpInterceptorsOptions>(opts =>opts.Interceptors.Add<TraceInterceptor>()
);
異常與狀態碼標記 ??
activity?.SetStatus(ActivityStatusCode.Error, message)
標記失敗 Spanactivity?.RecordException(ex)
記錄異常詳情
在 Jaeger UI 中直觀區分成功/失敗調用鏈。
日志與 Metrics 關聯 📑
-
日志:
builder.Logging.AddOpenTelemetry(logging =>logging.AddOtlpExporter());
-
Metrics:
- 自動采集請求計數與時延
- 自定義
Meter
導出 Prometheus
Jaeger 部署與高可用 🏗?
-
測試:All-in-One 鏡像,一鍵啟動
-
生產:
- 組件拆分:Agent/Collector/Query/UI 分離部署
- 后端存儲:Cassandra / Elasticsearch / Kafka
- 安全:啟用 TLS、鑒權(mTLS、Token),或通過 OTel Collector 做統一接入與流量控制
- 多副本:水平擴展與高可用
采樣策略與性能優化 🛡?
- ParentBasedSampler:跨服務一致決策
- Batch 模式:減少網絡與 CPU 開銷
- 動態調整:環境變量
OTEL_TRACES_SAMPLER
/OTEL_TRACES_SAMPLER_ARG
- 環境差異:開發環境
AlwaysOnSampler
;生產環境 5–10%
擴展:OTel Collector & Grafana Tempo 🔗
- Collector:統一接入、Filter、Auth、轉發至 Jaeger/Tempo/Prometheus
- Grafana Tempo:專注 Trace 存儲,結合 Prometheus、Loki 構建全棧 Observability