CodeGym /课程 /C# SELF /性能监控与指标采集

性能监控与指标采集

C# SELF
第 64 级, 课程 3
可用

1. 介绍

假设你是个网站管理员,每天有一千个用户访问你的网站。看日志的时候你发现一切正常,几乎没有错误(顶多有人偶尔忘记密码或验证码输错)。你会想「一切都很好!」。

然后有人告诉客服:网站特别卡,页面要 10 秒才打开。你去看日志——没有错误!是好事吗?不是。因为日志告诉你的是 发生了什么(或者没发生什么),但不会告诉你 有多快或多慢、用了多少资源、以及在负载增加时这个行为如何变化。

这时候指标就派上用场了——应用可测的运行特征。不只是错误数,还有平均响应时间、内存占用、每秒请求数等能反映系统健康的指标。

对比:
日志 — 是 「发生了什么」。
指标 — 是 「系统运行得好还是不好」。
追踪(tracing) — 是 「为什么系统这样工作(详细原因)」。

有哪些类型的指标,应该收集哪些?

主要指标类型:

指标类型 示例 用途
计数器 (Counters) 请求数、错误数、故障数 趋势、告警、负载
直方图 (Histograms) 响应时间、包大小 值分布、percentiles(百分位)
仪表 (Gauge) 内存使用、CPU 资源的当前状态
求和器 (Sum) 数据总量、字节数 一段时间内的总操作量

示例:

  • GET 请求响应时间的平均值和第 95 百分位。
  • 当前在线的用户数量。
  • 内存使用(Private Bytes, Working Set)。
  • 500/503 类错误的发生频率。
  • 每分钟的数据库请求数。

这些指标不仅能帮你发现问题,还能帮你 提前预防——因为服务器负载上升或响应时间缓慢增长通常预示着未来的故障。

2. .NET 与 OpenTelemetry 指标收集的工作机制

总体架构

在现代的 .NET(从 .NET 6 开始,尤其是 .NET 8/9)里,有基于 OpenTelemetry 的标准指标收集体系。

工作流程大致是这样的:

  1. 应用代码 调用方法来增加计数器、注册仪表、记录直方图等。
  2. OpenTelemetry Metrics SDK 在内存中收集这些指标并定期发送。
  3. 指标导出器 把它们传到选定的监控系统(Prometheus、Application Insights、Grafana Cloud、Datadog 等)。
  4. 监控后端 聚合、存储、可视化,并提供告警和仪表盘。
示意流程图:

[你的应用] 
       ⬇ 
 [指标收集 (OpenTelemetry SDK)]
       ⬇
 [指标导出器 (Prometheus, AI, Datadog, ...)]
       ⬇
 [监控系统/仪表盘/告警]

3. 实践:在 C# 中使用指标的基础

简单的内部指标: System.Diagnostics.Metrics

.NET 提供了内置的指标机制 — System.Diagnostics.Metrics。

主要角色:Meter, Counter<T>, Histogram<T>, ObservableGauge<T>。

示例:页面访问计数器


// 创建 Meter(通常整个应用只一个)
using System.Diagnostics.Metrics;

static Meter meter = new Meter("MyCompany.MyApp", "1.0");

// 注册计数器
static Counter<long> homePageVisits = meter.CreateCounter<long>("HomePageVisits");

// 在控制器或服务的某处...
public void HomePageRequested()
{
    homePageVisits.Add(1);
    // 页面处理的其余逻辑
}

注释:

  • Meter 是创建指标的「工厂」,有唯一名字(通常用应用/公司命名空间)。
  • CreateCounter<long> 创建计数器;通过 Add(1) 来递增。

示例:测量响应时间


static Histogram<double> pageLoadTime = meter.CreateHistogram<double>("PageLoadTimeMs");

// 在请求处理器中:
public void OnRequest()
{
    var stopwatch = System.Diagnostics.Stopwatch.StartNew();

    // ...这里是请求处理的主体...

    stopwatch.Stop();
    pageLoadTime.Record(stopwatch.Elapsed.TotalMilliseconds);
}

为动态值收集 Gauge

Gauge 是随时间变化的指标,比如在线用户数、当前内存占用等。


static ObservableGauge<int> onlineUserGauge = meter.CreateObservableGauge(
    "OnlineUsers", 
    () => GetOnlineUserCount());

// GetOnlineUserCount 是返回当前值的方法
static int GetOnlineUserCount()
{
    // 这里应该是你真实的逻辑!
    return ActiveUserList.Count;
}

在真实环境中这些都是异步工作的:应用提供指标值,导出器定期抓取并发送出去(比如 Prometheus 会 scrape "/metrics" endpoint)。

在现代 ASP.NET Core 应用中添加指标

对于 ASP.NET Core 来说很多东西开箱可用。只需要加入包 OpenTelemetry.Instrumentation.AspNetCore,就会产生 HTTP 请求、响应时间、错误数等指标。

在 Program.cs 中的配置示例:


using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyApp"))
            .AddAspNetCoreInstrumentation() // HTTP 指标
            .AddRuntimeInstrumentation()    // .NET CLR 的运行时指标
            .AddProcessInstrumentation()    // 进程的 CPU/内存 指标
            .AddMeter("MyCompany.MyApp")    // 你自己的 Meter
            .AddPrometheusExporter();       // 导出到 Prometheus
    });

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

现在你的应用会在 /metrics 地址暴露指标,可以被 Prometheus 或其他系统抓取。

4. 指标的实际使用示例

在真实项目中监控性能

接入指标可以帮你了解:

  • API 的平均和峰值 RPS(requests per second)能承受多少负载?
  • 瓶颈在哪里:某个 endpoint 平均 300 ms,另一个是 2000 ms?
  • DB 调用花了多少时间?(可以添加自定义的直方图)

示例:监控数据库查询时间


static Histogram<double> dbQueryDuration = meter.CreateHistogram<double>("DbQueryDurationMs");

public async Task<List<Product>> GetProductsAsync()
{
    var sw = Stopwatch.StartNew();
    var result = await _db.Products.ToListAsync();
    sw.Stop();
    dbQueryDuration.Record(sw.Elapsed.TotalMilliseconds);
    return result;
}

示例:统计错误次数


static Counter<long> apiErrors = meter.CreateCounter<long>("ApiErrors");

public IActionResult SomeEndpoint()
{
    try
    {
        // 某些操作
        return Ok();
    }
    catch (Exception)
    {
        apiErrors.Add(1);
        throw;
    }
}

为指标使用标签(tags/labels)

按有意义的维度对数据做分组很重要:endpoint、错误类型、用户类型等。


homePageVisits.Add(
    1, 
    KeyValuePair.Create<string, object>("UserType", "Admin"));

或者对直方图:


dbQueryDuration.Record(
    sw.Elapsed.TotalMilliseconds, 
    KeyValuePair.Create<string, object>("QueryType", "GetProducts"));

有了标签,在 Grafana 里你就可以按应用整体或具体细分维度画图。

5. 与 Prometheus、Application Insights、Datadog、Grafana 的集成

导出器与集成

  • Prometheus — 流行的开源监控方案,云和 Kubernetes 场景的事实标准。
  • Application Insights — Azure 的云端集成方案。
  • Datadog, Grafana Cloud — 专业级的基础设施方案。

这些系统都可以通过 OpenTelemetry 的导出器从 .NET 收集指标。关于 OTel 导出器的文档

Prometheus(步骤):

  1. 添加 NuGet 包: OpenTelemetry.Exporter.Prometheus。
  2. 在指标注册里添加 .AddPrometheusExporter()。
  3. 在 Grafana 配置数据源指向 Prometheus,并构建仪表盘。

有用链接:

6. 特性、坑和常见错误

一个常见的错误是 标签过度细化。如果给标签赋予太多唯一值(比如把用户 ID、订单 ID 当成标签),时间序列的数量会爆炸——这会导致指标存储被压垮、成本上升(也就是所谓的 cardinality explosion)。把标签做成「粗粒度」的。

开发者有时会忽略 System.Diagnostics.Metrics 和现成的工具,自己用日志加定时器去实现「轮子」。结果监控难以集成,也难以维护。尽量使用标准工具和自动化的 instrumentations。

另一个常见问题是 指标被收集了但没有导出。导出器的配置是必须的:比如添加 .AddPrometheusExporter(),并确保 /metrics endpoint 对 scraper 可访问。

最后,常见的混淆是 指标类型用错:有人用 Counter 去统计平均响应时间,但应该用 Histogram ——否则你看不到峰值和分布。Counters 用来计数;Histograms 用来记录时间/大小/分布;Gauges 用来表示当前状态。

2
任务
C# SELF, 第 64 级, 课程 3
已锁定
使用直方图测量操作执行时间
使用直方图测量操作执行时间
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION