.NET 项目集成可观测性:从架构思考到落地实践

本文基于一个真实的.NET 9项目集成可观测性栈的经历,分享从"为什么做"到"怎么落地"的完整过程。


一、为什么需要集成可观测性?

1.1 触发点:一个"慢"接口

项目中有一个业务接口,涉及多表关联、缓存查询、外部服务调用,业务联链条很长。上线后用户反馈"慢",但具体慢在哪一步?是数据库查询?是外部调用?还是序列化?靠日志很难定位,本地复现也困难。

我的诉求很简单:想上好监控,做到心里有数。

  • 接口整体耗时多少?哪些步骤最耗时?
  • 系统运行时的QPS、错误率、内存、CPU是什么水平?
  • 出问题时能快速定位,而不是靠猜。

这就是我们集成可观测性的起点------不是为了"高大上",而是为了解决一个具体的问题。


二、可观测性架构设计

此次集成,用到的核心组件有4个,OpenTelemetry,Prometheus,jaeger,grafana,在这之前,日志的部分我们已经集成到了ELK,所以整个可观测性的架构设计图如下

组件 作用 说明
OpenTelemetry Collector 数据中枢 接收应用推来的 OTLP 数据,分发给后端
Prometheus 指标存储 存 QPS、延迟、内存等时序数据
Jaeger 链路追踪 存每次请求的完整调用链
Grafana 可视化 把Prometheus和Jaeger的数据做成图表
ElasticSearch 日志存储 通过Logstash将系统产生的各类日志传输存储

注意:本篇后续不过多涉及ELK相关内容,这里点出来是因为和可观测性组件产生了交集。

*2.1 部署架构

我这里的数据可观测组件部署情况是,只在1台中心服务器上部署了Jaeger,Prometheus和Grafana,而所有应用节点服务器都通过Otel将数据推送到中心服务器,也就是webapi的trace/metric全部汇聚到中心服务器,只提供一个面板地址,同一排查。

这套架构在我这里的场景验证是有效的,适合中小型项目。

2.2 OpenTelemetry Collector:数据中枢

Collector是整个栈的核心枢纽,应用只跟它通信,它负责把数据分发给Prometheus和Jaeger。

核心配置

yaml 复制代码
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317        # 应用通过这个端口推数据过来

processors:
  batch:
    timeout: 5s                        # 攒批超时,减少网络开销
  memory_limiter:
    limit_mib: 512                     # 防止数据堆积导致OOM

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"          # 暴露给Prometheus抓取
  otlp/jaeger:
    endpoint: 127.0.0.1:14250         # traces推给Jaeger
    tls:
      insecure: true                   # 内网不需要TLS

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      exporters: [otlp/jaeger]

关键点

  • receivers 定义数据怎么进来(OTLP gRPC)
  • exporters 定义数据往哪去(Prometheus收指标,Jaeger收traces)
  • processors 做批量和内存保护
  • 注意不要加 namespace 前缀,否则Grafana查询metric名会不匹配

2.3 Prometheus:指标存储

Prometheus 定时去 OTel Collector 拉数据(pull 模式)。

核心配置

yaml 复制代码
global:
  scrape_interval: 15s                 # 每 15 秒抓一次

scrape_configs:
  - job_name: "otel-collector"
    static_configs:
      - targets: ["localhost:8889"]    # Collector的Prometheus exporter端口

启动参数

bash 复制代码
prometheus.exe --config.file=prometheus.yml --storage.tsdb.retention.time=30d
  • retention.time=30d:数据保留 30 天,自动清理

2.4 Jaeger:链路追踪

Jaeger负责存储和查询traces,可以看到每次请求的完整调用链。

核心配置(Jaeger v2格式):

yaml 复制代码
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:14250       # 接收 Collector 推送的 traces

exporters:
  jaeger_storage_exporter:             # 写入存储(必须!否则UI为空)
    trace_storage: jaeger_storage

extensions:
  jaeger_query:                        # Web UI
    storage:
      traces: jaeger_storage
  jaeger_storage:                      # 内存存储后端
    backends:
      jaeger_storage:
        memory:
          max_traces: 100000

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [jaeger_storage_exporter]  # 必须包含!

踩坑提醒 :traces pipeline必须包含 jaeger_storage_exporter,否则Jaeger UI里Service下拉为空,这是最常见的坑。

2.5 Grafana:可视化

Grafana负责把数据做成图表,配置两个数据源即可:

yaml 复制代码
datasources:
  - name: Prometheus
    type: prometheus
    url: http://localhost:9090
    isDefault: true
  - name: Jaeger
    type: jaeger
    url: http://localhost:16686

部署顺序(很重要):

plain 复制代码
1. Jaeger          ← 必须先启动(Collector 要连它的 14250)
2. OTel Collector   ← 接收应用数据
3. Prometheus       ← 抓取指标
4. Grafana          ← 可视化

上述服务部署后控制台输出效果分别如下

  • jaeger服务启动后控制台输出
  • 启动Otel Collector后传输数据效果如下
  • 启动Prometheus后控制台输出效果如下
  • grafana启动后控制台输出

三、项目代码

3.1 和OpenTelemetry通信

.NET项目里只需要做一件事:通过OTel SDK把数据推给OTel Collector。后面的Prometheus、Jaeger、Grafana都是接收和展示,不需要应用关心。

NuGet 包

xml 复制代码
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />

服务注册(约 30 行代码):

csharp 复制代码
private static void ConfigureOpenTelemetry(this WebApplicationBuilder builder)
{
    string otlpEndpoint = builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]
        ?? "http://localhost:4317";

    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing =>
                     {
                         tracing
                             .AddAspNetCoreInstrumentation()          // 自动采集HTTP请求traces
                             .AddSource("Declaration.*")                    // 通配自定义Span
                             .ConfigureResource(r => r.AddService("Magic.Declaration.WebAPI"))
                             .AddOtlpExporter(options =>
                                 {
                                     options.Endpoint = new Uri(otlpEndpoint);
                                     options.ExportProcessorType = ExportProcessorType.Batch;
                                 });
                     })
        .WithMetrics(metrics =>
                     {
                         metrics
                             .AddAspNetCoreInstrumentation()          // 自动采集HTTP请求指标
                             .AddRuntimeInstrumentation()             // .NET运行时指标(GC、CPU、内存)
                             .ConfigureResource(r => r.AddService("Magic.Declaration.WebAPI"))
                             .AddOtlpExporter(options =>
                                 {
                                     options.Endpoint = new Uri(otlpEndpoint);
                                 });
                     });
}

采集信息类别

类别 指标 说明
HTTP 请求 QPS、延迟分布、状态码 Instrumentation.AspNetCore 自动采集
.NET 运行时 GC、CPU、内存、线程池 Instrumentation.Runtime 自动采集

3.2 开发环境

为了保持开发环境的高效,且能看到采集到的基本数据,我们可以在开发环境配置将采集的数据输出到控制台。

csharp 复制代码
if (builder.Environment.IsDevelopment())
{
    tracing.AddConsoleExporter();  // traces 输出到控制台
}

启动应用后,控制台会直接输出每次请求的 trace 信息:

plain 复制代码
Activity.TraceId:            8d35e...
Activity.SpanId:             a7f2e...
Activity.DisplayName:        GET /api/order/detail
Activity.Kind:               Server
Activity.Duration:           00:00:00.1234
Resource associated with Activity:
    service.name: MyApp.WebAPI

效果如下

开发阶段用控制台就够了,不用装任何额外组件。

3.3 可拔插设计

这套可观测性栈是锦上添花型接入,不是项目运行的必要条件:

  • 不装 OTel Collector:应用正常跑,只是不采集数据
  • 不装 Prometheus/Jaeger:Collector 可以只做控制台输出
  • 环境变量不配 :代码里有默认值 http://localhost:4317

对业务代码零侵入:所有配置都收敛在服务注册阶段,业务代码不需要任何改动。


四、怎么验证和分析采集到的数据

4.1 验证数据流

部署完成后,先验证数据是否流通:

bash 复制代码
# 1. 检查 Collector 是否在接收数据
curl http://localhost:8888/metrics | findstr "accepted_spans"

# 2. 检查 Prometheus 是否有指标
curl "http://localhost:9090/api/v1/query?query=http_server_request_duration_seconds_count"

# 3. 检查 Jaeger 是否有 traces
curl http://localhost:16686/api/services

4.2 Jaeger:链路追踪分析

Jaeger UI地址:http://localhost:16686

定位链条

打开Jaeger面板后,点击某条Trace,展开看完整的调用链:

plain 复制代码
[HTTP GET /api/order/detail]  120ms
  ├── [MyApp.CreateOrder]       95ms    ← 你的自定义 Span
  │     ├── [FreeSql Query]      80ms   ← 数据库查询
  │     └── [Redis Get]           5ms   ← 缓存读取
  └── [HTTP Response]            10ms

Tips

链路追踪数据除了在Jaeger面板查看,也可以集成到Grafana,统一管理,本篇受篇幅限制不在赘述,下面分别给出Jaeger和Grafana的面板效果。

  • jaeger面板
  • grafana面板

常见场景

场景 操作
接口慢但不知道慢在哪 搜该接口的Trace → 看哪个Span耗时最长
间歇性超时 搜Duration > 1s的Trace → 对比正常和异常的差异
500 错误 搜Status = ERROR的Trace → 看Logs中的异常信息

4.3 Grafana:配置Dashboard

Grafana UI地址:http://localhost:3000

第一步:添加数据源(如果用 provisioning 文件部署则自动配置)

Configuration → Data Sources → Add data source → 选 Prometheus 和 Jaeger

第二步:创建 Dashboard

推荐先建一个包含以下面板的Dashboard:

面板 数据源 PromQL
实时QPS Prometheus sum(rate(http_server_request_duration_seconds_count[1m]))
P95延迟 Prometheus histogram_quantile(0.95, sum(rate(http_server_request_duration_seconds_bucket[5m])) by (le))
错误率 Prometheus sum(rate(http_server_request_duration_seconds_count{http_response_status_code=~"5.."}[5m])) / sum(rate(http_server_request_duration_seconds_count[5m]))
内存占用 Prometheus dotnet_gc_last_collection_heap_size_bytes
CPU使用率 Prometheus rate(dotnet_process_cpu_time_seconds_total[1m])

第三步:按路由分组

想看每个接口的独立指标,用 by (http_route) 分组:

plain 复制代码
sum(rate(http_server_request_duration_seconds_count[5m])) by (http_route, http_request_method)

Dashboard 设计原则

  • 顶部放概览数字(QPS、延迟、错误率、CPU)
  • 中间放趋势图(按时间变化)
  • 底部放明细表(按接口分组)

Grafana效果

注意:以上统计的方法,均可在Grafana社区官网查询到,也可以在这里(grafana.com/grafana/das...


五、总结

集成可观测性的本质是让系统运行状态可见,而不是靠猜。

四个组件各司其职

  • OpenTelemetry Collector 做数据中枢
  • Prometheus 存指标
  • Jaeger 存链路
  • Grafana 做展示

.NET 项目要做的很少

  • 接入 OTel SDK,配置 OTLP 导出地址
  • 自动采集 HTTP 请求和运行时指标
  • 需要时加自定义 Span 定位业务逻辑
  • 开发阶段控制台就能看,生产环境按需部署

可拔插、零侵入

  • 不装组件不影响业务运行
  • 配置都在服务注册阶段,业务代码无感
  • 环境变量控制接入程度

可观测性不是一次性任务,而是持续的过程------先跑起来,遇到问题再逐步深入。


相关推荐
梨子同志1 小时前
PostgreSQL
后端
金智维科技官方1 小时前
AI驱动的应付账款自动化,落地时要拆解哪些流程?
运维·人工智能·自动化
写代码的学渣2 小时前
Linux 彻底清空历史命令 & 恢复历史命令(完整版、重启永久生效)
linux·运维·chrome
AskHarries2 小时前
用 OpenClaw 做视频脚本:选题、分镜、旁白和镜头表
后端·程序员
fei_sun2 小时前
ARP(地址解析协议)与NDP(邻居发现协议)
运维·服务器
狂炫冰美式2 小时前
凌晨睡不着,我给台风巴威写了个追踪网站
前端·后端·github
程序员cxuan3 小时前
Claude Code 焚诀(一):Claude Code 六种心智模型
人工智能·后端
雪隐3 小时前
用Flutter做背单词APP-01服务器?不存在的,我家里就有
前端·人工智能·后端
Qimooidea3 小时前
祁木 CAD Translator 全套功能 & 官网入口汇总
运维·服务器