一、健康检查的概念

在分布式应用架构中,健康检查是确保系统稳定性的关键机制。健康检查通过定期向应用发送请求来监控其可用性和依赖服务的状态,使得容器编排器(如Kubernetes)和负载均衡器能够做出智能的流量调度决策。当一个服务不健康时,编排器会停止向其路由请求,甚至可能重启该实例以恢复服务。

在.NET Aspire框架中,健康检查机制内置于AppHost的资源管理系统。AppHost在启动资源时会等待依赖资源的健康检查通过,确保服务依赖关系正确建立。这种机制特别重要在零停机部署场景中,只有当一个服务变为健康状态时,编排器才会开始向新实例路由流量。

健康检查通常包括三个关键指标:Liveness(存活性)、Readiness(就绪性)和Startup(启动)。Liveness用于检测应用是否仍在运行,即使某些内部依赖出现问题。Readiness检查应用是否准备好接收流量,包括所有依赖服务的验证。Startup检查应用是否已完成初始化过程,特别是对于需要长时间启动的应用至关重要。

二、内置健康检查

.NET Aspire提供了开箱即用的健康检查机制,基于ASP.NET Core的Microsoft.Extensions.Diagnostics.HealthChecks命名空间。在AppHost项目中,你可以通过简单的扩展方法为资源配置健康检查。

最基础的健康检查配置是使用WithHttpHealthCheck方法。当资源添加了此配置后,AppHost会定期向指定的HTTP端点发送请求,根据返回的HTTP 200状态码判断服务是否健康。在这个例子中,我们首先通过DistributedApplication.CreateBuilder创建一个AppHost应用构建器,这是所有Aspire应用的入口点。随后,AddContainer方法添加一个名为catalog-api的容器资源,第二个参数指定了容器镜像名称。WithHttpEndpoint方法为容器配置了HTTP暴露端口,targetPort: 8080指定容器内部监听的端口。最关键的是WithHttpHealthCheck(“/health”)方法,它告诉AppHost定期向容器的/health端点发送HTTP请求来检查服务健康状态。

var builder = DistributedApplication.CreateBuilder(args);

var catalogApi = builder.AddContainer("catalog-api", "catalog-api")
                        .WithHttpEndpoint(targetPort: 8080)
                        .WithHttpHealthCheck("/health");

builder.AddProject<Projects.WebApplication1>("store")
       .WithReference(catalogApi.GetEndpoint("http"))
       .WaitFor(catalogApi);

builder.Build().Run();

在这个例子中,catalog-api容器配置了健康检查端点。接下来,AddProject方法添加了store项目资源,它是一个WebApplication1项目。WithReference方法建立了store对catalogApi的引用,通过GetEndpoint(“http”)获取HTTP端点的连接字符串,这样store可以通过服务发现来访问catalog-api。最重要的是WaitFor(catalogApi)方法,它创建了显式的依赖关系声明,告诉AppHost不要在catalog-api健康检查通过之前启动store项目。AppHost在启动流程中会等待catalog-api的/health端点返回HTTP 200状态码,然后才会继续启动store。这种机制确保了依赖服务必须先于依赖者运行,避免了服务启动顺序问题导致的连接失败。

在被监控的ASP.NET Core应用中,你需要配置对应的健康检查端点。最简单的配置方式是在Program.cs中注册和映射健康检查端点。AddHealthChecks()方法向依赖注入容器中注册健康检查服务。这个方法返回一个IHealthChecksBuilder实例,允许你进一步配置具体的健康检查。如果不添加任何特定的检查实现,框架会使用默认行为。MapHealthChecks(“/health”)方法在指定的路由上创建一个HTTP端点,当外部系统请求此端点时,框架会执行所有注册的健康检查并返回结果。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/health");

app.Run();

这个配置会在/health端点创建一个基础的健康检查探针。该探针会报告应用的可用性状态。当AppHost向/health发送GET请求时,如果应用运行正常,端点会立即响应HTTP 200 OK,响应体为"Healthy"。默认情况下,如果应用能够响应请求,它就被视为健康。这种简单的活性检查(Liveness Probe)足以满足大多数场景,用于验证应用进程是否仍在运行。

.NET Aspire中的内置健康检查还支持为特定端点类型的自动配置。例如,WithHttpHealthCheck方法会自动为应用绑定的HTTP端点创建健康检查。如果应用同时配置了HTTP和HTTPS端点,Aspire 9.3版本及以后会优先选择HTTPS端点。

健康检查的可靠性至关重要。在AppHost中,你可以配置健康检查的超时和重试策略。AppHost仪表板会实时显示每个资源的健康状态,以及健康检查报告的具体信息。这为开发者提供了清晰的可视化反馈,帮助诊断启动问题。

三、自定义健康检查

除了HTTP端点健康检查,.NET应用通常需要检查外部依赖的状态,如数据库、缓存服务、消息队列等。实现自定义健康检查需要实现IHealthCheck接口,该接口定义了CheckHealthAsync方法。

创建一个自定义健康检查类的基本结构如下。DatabaseHealthCheck类实现了IHealthCheck接口,这是.NET健康检查框架的核心抽象。构造函数接收IDbConnection注入,通过依赖注入获得数据库连接实例。CheckHealthAsync方法是核心逻辑,它在检查执行时被调用。方法签名中的HealthCheckContext参数包含当前检查的元数据和配置信息,CancellationToken参数允许优雅地取消长时间运行的检查。在try块中,我们首先调用await _connection.OpenAsync(cancellationToken)来打开数据库连接,这个异步操作会立即返回连接状态。然后执行一个简单的SELECT 1查询来验证数据库真正可用且可执行命令。关键是这个查询非常轻量,不会对数据库造成负担。最后调用_connection.Close()关闭连接,避免连接泄漏。如果所有操作成功,返回HealthCheckResult.Healthy()表示检查通过。如果任何操作抛出异常,catch块会捕获异常并返回HealthCheckResult.Unhealthy(),传入exception参数便于诊断。

using Microsoft.Extensions.Diagnostics.HealthChecks;

public class DatabaseHealthCheck : IHealthCheck
{
    private readonly IDbConnection _connection;

    public DatabaseHealthCheck(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        try
        {
            // 执行一个简单的数据库查询来验证连接
            await _connection.OpenAsync(cancellationToken);
            await _connection.ExecuteAsync("SELECT 1", cancellationToken: cancellationToken);
            _connection.Close();

            return HealthCheckResult.Healthy("Database connection is healthy.");
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy(
                description: "Database connection failed.",
                exception: ex);
        }
    }
}

在Program.cs中注册这个自定义健康检查时,AddHealthChecks()返回的IHealthChecksBuilder提供了AddCheck泛型方法,允许你注册实现IHealthCheck接口的任何类。name参数给检查起个唯一的标识符,在日志和仪表板中会用到。failureStatus参数指定了健康检查失败时返回的状态,HealthStatus.Unhealthy表示服务无法运行不能提供服务,而HealthStatus.Degraded表示服务仍能运行但性能下降。tags参数是一个字符串数组,用于对健康检查进行分类,在后续创建多个健康检查端点时可以使用Predicate根据tag来筛选执行哪些检查。例如这里的"ready"和"db" tag将被用于分离就绪性检查和存活性检查。

builder.Services.AddHealthChecks()
    .AddCheck<DatabaseHealthCheck>(
        name: "Database",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "ready", "db" });

对于常见的外部服务,AspNetCore.Diagnostics.HealthChecks NuGet包提供了预制的健康检查实现。这些预制检查已经针对特定服务进行了优化,处理了服务特定的超时和异常情况。例如,检查SQL Server数据库时,AddSqlServer方法会使用提供的连接字符串创建一个新的SqlConnection,发送一个轻量的"SELECT 1"命令来验证连接状态。connectionString参数从应用配置中读取,这允许不同的环境使用不同的数据库实例。name参数是这个检查的标识符,tags参数用于分类,这里的"database"标签表示这是一个数据库相关的检查。

builder.Services.AddHealthChecks()
    .AddSqlServer(
        connectionString: configuration["ConnectionString"],
        name: "SqlServer-check",
        tags: new string[] { "database" });

这个包还提供了Azure Blob Storage、Azure Service Bus、RabbitMQ等服务的健康检查实现,应用开发者可以轻松地检查各种外部依赖。对于Redis缓存,AddRedis方法会连接到Redis服务器并执行PING命令来验证连接性。redisConnectionString参数指定了Redis实例的地址和端口信息。这种方式比自己手写健康检查更可靠,因为官方实现已经考虑了各种边界情况和异常处理。

builder.Services.AddHealthChecks()
    .AddRedis(
        redisConnectionString: configuration["RedisConnection"],
        name: "Redis-check",
        tags: new string[] { "cache" });

除了HTTP端点检查,你也可以在AppHost中定义自定义的健康检查逻辑。WithHealthCheck()方法是一个便捷的扩展方法,它为资源自动配置适当的健康检查。对于Redis等容器资源,Aspire框架知道如何验证其健康状态。AddRedis(“cache”)添加了一个名为cache的Redis容器资源。WithHealthCheck()方法为这个Redis实例配置了健康检查,AppHost会自动知道应该使用PING命令来验证Redis的可用性。然后在myapp项目中通过WithReference(cache)建立了对cache的引用,这会自动为应用注入Redis连接字符串的环保变量。最关键的是WaitFor(cache)方法,它创建了从myapp到cache的依赖关系,AppHost会确保cache的健康检查通过后再启动myapp。这种声明式的依赖关系管理大大简化了微服务启动编排的复杂性。

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache")
    .WithHealthCheck();

var myapp = builder.AddProject<Projects.MyApp>("myapp")
    .WithReference(cache)
    .WaitFor(cache);

builder.Build().Run();

健康检查可以配置延迟执行和执行周期。这个配置对于某些服务特别重要,因为它们可能需要一段时间才能完成初始化。Delay参数表示资源启动后多久才开始首次健康检查,例如一个应用可能需要10秒时间来加载所有数据和初始化状态。Period参数表示两次检查之间的时间间隔,这里设置为30秒意味着如果第一次检查失败,AppHost会等待30秒后再次尝试。HealthCheckRegistration构造函数的name参数给检查指定唯一标识符,instance参数传入实现IHealthCheck接口的实例,failureStatus参数指定失败时的状态。这种配置方式允许对每个检查进行精细控制,特别适合处理那些启动缓慢或需要定期验证的服务。

var registration = new HealthCheckRegistration(
    name: "CustomCheck",
    instance: new CustomHealthCheck(),
    failureStatus: HealthStatus.Unhealthy,
    tags: null)
{
    Delay = TimeSpan.FromSeconds(10),
    Period = TimeSpan.FromSeconds(30)
};

builder.Services.AddHealthChecks().Add(registration);

四、健康检查的聚合

在微服务架构中,单个服务的健康状态需要被聚合成整体的应用健康状态。这种聚合机制允许外部系统快速了解应用的整体状况,而不必逐个查询每个服务。

在ASP.NET Core中,健康检查聚合通过HealthCheckOptions的Predicate属性实现。Predicate是一个委托函数,用于筛选哪些注册的健康检查应该在特定的端点被执行。这个强大的机制允许你基于不同的业务需求为同一个应用创建多个健康检查端点。MapHealthChecks的第一个参数是路由路径,第二个参数是HealthCheckOptions配置对象。在"/healthz/ready"端点上,Predicate使用Lambda表达式healthCheck => healthCheck.Tags.Contains(“ready”)来只执行带有"ready"标签的检查。这种就绪性检查通常包括验证数据库连接、缓存可用性等重要依赖,用于判断应用是否准备好接收流量。在"/healthz/live"端点上,Predicate返回false意味着不执行任何具体的健康检查,这个端点只是返回应用是否运行,用于Kubernetes的liveness probe来检测应用进程是否仍然存在。这种分离很重要,因为liveness probe失败会导致容器重启,而readiness probe失败只是暂时停止转发流量。启动检查端点"/healthz/startup"用于检查应用初始化是否完成,只执行带有"startup"标签的检查。

// 就绪检查:检查应用是否准备好接收流量
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
{
    Predicate = healthCheck => healthCheck.Tags.Contains("ready")
});

// 存活性检查:仅检查应用是否运行
app.MapHealthChecks("/healthz/live", new HealthCheckOptions
{
    Predicate = _ => false  // 不执行任何具体检查
});

// 启动检查:检查初始化是否完成
app.MapHealthChecks("/healthz/startup", new HealthCheckOptions
{
    Predicate = healthCheck => healthCheck.Tags.Contains("startup")
});

在注册健康检查时,通过tags参数来分类。这个例子中定义了三个不同的检查,每个检查都有不同的标签用于在不同的端点中被选择性地执行。AddCheck方法的第二个参数是一个Func<Task>委托,允许你使用Lambda表达式定义简单的健康检查逻辑。StartupCheck有"startup"标签,会在启动检查端点执行。ReadinessCheck有"ready"标签,会在就绪检查端点执行。LivenessCheck有"live"标签,虽然在这个例子中不会被执行(因为live端点的Predicate返回false),但如果你后续添加新的端点来执行"live"标签的检查,这个检查就会被触发。这种标签化的设计使得健康检查框架具有良好的可扩展性。

builder.Services.AddHealthChecks()
    .AddCheck("StartupCheck", 
        () => HealthCheckResult.Healthy(), 
        tags: new[] { "startup" })
    .AddCheck("ReadinessCheck",
        () => HealthCheckResult.Healthy(),
        tags: new[] { "ready" })
    .AddCheck("LivenessCheck",
        () => HealthCheckResult.Healthy(),
        tags: new[] { "live" });

Kubernetes集群特别依赖于这种分离的健康检查。在Kubernetes的Pod配置中,你会将不同的检查端点映射到对应的探针类型:

spec:
  containers:
  - name: myapp
    livenessProbe:
      httpGet:
        path: /healthz/live
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /healthz/ready
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5
    startupProbe:
      httpGet:
        path: /healthz/startup
        port: 80
      failureThreshold: 30
      periodSeconds: 10

在.NET Aspire的AppHost中,健康检查聚合通过等待链实现。多个资源可以依赖于同一个资源,AppHost会确保该资源的健康检查在所有依赖的资源启动前通过。这个例子演示了一个更复杂的场景,其中应用api依赖于两个基础设施资源。AddSqlServer(“sql”)添加了一个SQL Server容器资源,WithHealthCheck()为其配置了健康检查。类似地,AddRedis(“cache”)添加了Redis容器,也配置了健康检查。在api项目中,WithReference(db)和WithReference(cache)分别建立了对数据库和缓存的引用,这会为应用自动配置相关的连接字符串环境变量。更重要的是WaitFor(db)和WaitFor(cache)方法链,它们显式声明了依赖关系。AppHost启动流程会首先启动db和cache,等待它们的健康检查通过后,再启动api项目。这种声明式的依赖关系管理避免了应用启动时连接到不可用服务的问题,确保了服务启动顺序的正确性。

var db = builder.AddSqlServer("sql")
    .WithHealthCheck();

var cache = builder.AddRedis("cache")
    .WithHealthCheck();

var api = builder.AddProject<Projects.Api>("api")
    .WithReference(db)
    .WithReference(cache)
    .WaitFor(db)
    .WaitFor(cache);

健康检查的响应格式也很重要。默认的响应是简单的文本格式,例如仅返回"Healthy"、“Degraded"或"Unhealthy”,但你可以自定义ResponseWriter来生成结构化的JSON响应,便于外部监控系统解析。ResponseWriter是一个Func<HttpContext, HealthReport, Task>委托,允许你完全控制HTTP响应。在这个例子中,首先设置context.Response.ContentType为"application/json"告诉客户端响应是JSON格式。然后创建一个匿名对象来构造响应体,status字段包含整体健康状态。更重要的是checks字段,它使用LINQ的Select方法遍历report.Entries中的每个检查结果。对于每个检查,我们提取name(检查名称)、status(检查状态)、duration(检查耗时)、description(描述信息)和exception?.Message(如果有异常,包含异常消息)。最后使用WriteAsJsonAsync将整个对象序列化为JSON并发送给客户端。这样外部监控系统就可以收到详细的检查结果,而不仅仅是整体状态,便于精确的故障诊断。

app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        var response = new
        {
            status = report.Status.ToString(),
            checks = report.Entries.Select(entry => new
            {
                name = entry.Key,
                status = entry.Value.Status.ToString(),
                duration = entry.Value.Duration.TotalMilliseconds,
                description = entry.Value.Description,
                exception = entry.Value.Exception?.Message
            })
        };
        await context.Response.WriteAsJsonAsync(response);
    }
});

五、指标(Metrics)收集

指标是可观测性的第二个支柱,用于跟踪应用和系统的各项数值。.NET使用System.Diagnostics.Metrics命名空间提供的Meter API来创建和记录指标。与健康检查关注的是服务可用性不同,指标关注的是应用的性能表现。

在.NET应用中,创建自定义指标的基础步骤是定义一个Meter实例,然后创建具体的指标收集器。Meter是指标的逻辑容器,第一个参数"MyApp.Metrics"是Meter的名称,通常使用应用名称作为前缀以便在集聚式监控系统中区分来源。第二个参数"1.0.0"是版本号,用于追踪指标定义的演变。CreateCounter创建一个计数器,用于只增不减的累加统计,这里用来计总请求数。counter的名称"http.requests.total"遵循了OpenTelemetry的命名规范,便于监控系统识别。unit参数"requests"指定了计量单位。CreateHistogram创建一个直方图,用于记录值的分布,这里用于记录请求延迟,单位是毫秒。直方图会自动将数据点归类到预定义的桶中,便于计算百分位数如P50、P95、P99。CreateUpDownCounter创建一个上下计数器,可以增加也可以减少,适合记录当前活跃连接数这种动态变化的指标。

using System.Diagnostics.Metrics;

// 创建一个Meter用于应用指标
var appMeter = new Meter("MyApp.Metrics", "1.0.0");

// 创建一个计数器来记录请求次数
var requestCounter = appMeter.CreateCounter<int>(
    "http.requests.total",
    unit: "requests",
    description: "Total number of HTTP requests");

// 创建一个直方图来记录请求延迟
var requestDuration = appMeter.CreateHistogram<double>(
    "http.request.duration",
    unit: "ms",
    description: "HTTP request duration in milliseconds");

// 创建一个仪表来记录活跃连接数
var activeConnections = appMeter.CreateUpDownCounter<int>(
    "http.connections.active",
    description: "Number of active HTTP connections");

在ASP.NET Core应用中,可以在中间件中记录这些指标。这个中间件处理每个HTTP请求,首先记录startTime用于后续计算延迟。在try块中,activeConnections.Add(1)记录新连接打开,increment操作立即增加计数。接着调用await next()让请求流向下一个中间件和路由处理器。当请求返回时,requestCounter.Add()记录请求计数,并通过KeyValuePair添加一个tag"method"来标记HTTP方法,这允许在监控系统中按方法(GET、POST等)分别聚合统计。最重要的是finally块中的操作,即使发生异常也会执行。这里计算elapsed为请求耗时,然后调用requestDuration.Record()记录到直方图,同时添加"route"标签来标记请求路径。最后调用activeConnections.Add(-1)来减少活跃连接计数。这个完整的生命周期覆盖确保了所有指标都被准确记录,even when errors occur。

app.Use(async (context, next) =>
{
    var startTime = DateTime.UtcNow;
    
    try
    {
        // 记录连接打开
        activeConnections.Add(1);
        
        await next();
        
        // 记录请求
        requestCounter.Add(1, new KeyValuePair<string, object?>(
            "method", context.Request.Method));
    }
    finally
    {
        // 记录持续时间
        var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
        requestDuration.Record(elapsed, 
            new KeyValuePair<string, object?>("route", context.Request.Path));
        
        // 记录连接关闭
        activeConnections.Add(-1);
    }
});

.NET Aspire框架已经为所有项目自动配置了OpenTelemetry的指标收集,这是Aspire的核心优势之一。在ServiceDefaults项目的Extensions.cs文件中,会自动添加ASP.NET Core、HTTP客户端和运行时的指标。WithMetrics是一个扩展方法,接收一个配置委托参数。AddAspNetCoreInstrumentation()添加了ASP.NET Core框架本身的指标收集,包括HTTP请求计数、延迟、异常等。AddHttpClientInstrumentation()添加了HttpClient的指标,用于追踪应用发出的外部HTTP调用。AddRuntimeInstrumentation()添加了.NET运行时的指标,包括GC信息、内存使用、线程数等。这三个instrumentation配置组成了一个完整的基础指标栈,适合大多数应用。

builder.WithMetrics(metrics =>
{
    metrics.AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddRuntimeInstrumentation();
});

这意味着你的Aspire应用会自动收集以下类型的指标:HTTP请求延迟、吞吐量、数据库连接池状态、内存使用情况等。这些指标通过OTLP(OpenTelemetry Protocol)协议导出到Aspire仪表板。OTLP是一个标准的、厂商无关的协议,确保遥测数据能够互操作。

对于更细粒度的控制,你可以在ServiceDefaults中添加额外的指标源。AddMeter方法允许你注册特定的Meter名称,使其指标被OpenTelemetry collector收集。首先添加你在应用中定义的自定义Meter “MyApp.Metrics”,这样你之前创建的requestCounter、requestDuration等指标就会被包含在导出中。然后添加"System.Net.Http"和"System.Net.NameResolution"这两个系统级Meter来收集网络层的更多细节指标。这种分层的方式既支持自动化的开箱即用配置,也支持对特定指标的精细控制。

builder.WithMetrics(metrics =>
{
    metrics.AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddRuntimeInstrumentation()
        .AddMeter("MyApp.Metrics")  // 添加自定义Meter
        .AddMeter("System.Net.Http")  // 添加网络层指标
        .AddMeter("System.Net.NameResolution");  // 添加DNS解析指标
});

指标的核心概念包括几个类型:Counter(计数器,只增不减)、UpDownCounter(上下计数器,可增可减)、Histogram(直方图,记录分布)和Gauge(仪表,记录瞬时值)。选择合适的类型对于准确反映应用状态至关重要。

六、.NET Aspire 仪表板监控

.NET Aspire仪表板是一个强大的可视化工具,专为本地开发循环中的实时监控而设计。当你按F5运行Aspire的AppHost项目时,仪表板会自动启动并提供对所有运行资源的全面洞察。

仪表板通过OTLP(OpenTelemetry Protocol)接收来自应用的遥测数据。AppHost会自动为所有.NET项目注入OpenTelemetry环境变量,配置它们向仪表板的OTLP端点发送数据。这个过程完全自动化,开发者不需要手动配置。

仪表板的资源页面展示了应用中所有已配置的资源,包括它们的健康状态、依赖关系和运行日志。每个资源都有一个代表其服务类型的图标,并以不同的颜色代表其遥测数据状态。资源之间的关系通过连接线可视化,帮助开发者理解应用架构。

在Aspire 9.2版本中,仪表板引入了资源图视图,可以更清楚地展示资源之间的依赖关系。你可以在资源页面点击"Graph"选项卡查看这个关系图,这对于理解复杂的分布式应用特别有帮助。

仪表板的指标(Metrics)页面显示应用收集的所有数值指标。你可以选择特定的资源并浏览它的指标。指标被分组展示,包括HTTP请求统计、内存使用、GC信息等。对于应用定义的自定义指标,仪表板也会自动发现和展示。

// 仪表板会自动收集和显示这些指标
var meter = new Meter("MyService", "1.0.0");
var customMetric = meter.CreateCounter<int>("custom.operations", description: "Custom operation counter");

// 仅需在应用中记录指标,仪表板就会自动展示
customMetric.Add(1);

这个代码片段展示了Aspire仪表板的自动化能力。你定义了一个名为"MyService"的Meter和一个名为"custom.operations"的Counter。然后在应用运行时调用customMetric.Add(1)来增加计数。只要这个Meter被添加到ServiceDefaults的WithMetrics配置中(通过.AddMeter(“MyService”)),OpenTelemetry会自动收集这个指标并导出到仪表板。你无需任何额外配置,仪表板就会立即发现并展示这个新指标。这种零配置的自动发现和可视化是Aspire相比于其他监控方案的重要优势。

跟踪(Traces)页面展示了分布式追踪信息,记录了跨服务的请求流程。每条追踪包含多个span,表示操作的不同阶段。通过追踪,你可以看到HTTP请求从一个服务到另一个服务的完整路径,包括各个服务的处理时间。这对于性能诊断和故障排查非常有价值。

在追踪详情页面,你可以看到每个span的属性和标签。例如,HTTP span会包含请求方法、路径、响应状态码等信息。如果span包含exemplars(关联的具体数据点),点击这些点可以导航到对应的请求,进一步深入调查性能问题。

日志(Logs)页面分为Console Logs(控制台日志)和Structured Logs(结构化日志)两部分。结构化日志使用OpenTelemetry日志API发送,包含更多的上下文信息,如日志级别、时间戳和自定义属性。Console Logs则是应用直接打印到控制台的输出。

仪表板还支持按资源、日志级别和文本内容过滤日志。这对于在处理许多并发请求时快速定位问题特别有用。你可以在日志选项中启用UTC时间戳显示,便于跨时区的调试。

在Aspire 9.2版本,仪表板新增了暂停/恢复遥测收集的功能。点击暂停按钮可以冻结当前显示的数据,便于仔细分析,而应用继续运行。这个功能在需要关注特定时间段的数据时非常有用。

仪表板还支持遥测过滤。你可以按照属性值过滤追踪,例如,仅查看特定HTTP路由或特定错误代码的追踪。过滤功能支持自动完成,建议已有的属性值,减少输入错误。

对于部署环境,Aspire提供了独立的仪表板Docker容器,可以接收来自任何使用OTLP协议的应用的遥测数据。这使得Aspire不仅限于本地开发,还可以用于生产环境的监控。

七、告警配置

虽然.NET Aspire本身不提供内置的告警系统,但它可以与外部监控系统(如Prometheus和Grafana)集成,实现告警功能。

对于本地开发环境,Aspire仪表板的实时展示已经足以进行监控和告警。但对于需要自动化告警的场景,你可以将仪表板连接到Prometheus进行指标收集。

首先,配置应用导出指标到Prometheus。在ServiceDefaults的配置中,.WithMetrics()配置了标准的instrumentation就像之前一样。关键的新增是.AddPrometheusHttpListener()方法,它启用了一个HTTP监听器来导出Prometheus格式的指标。Prometheus格式是一个纯文本格式,每行包含一个指标的名称、标签和值。这个监听器默认在端口9184上启动一个HTTP服务器,当Prometheus服务器访问这个端点时,就可以抓取所有累积的指标数据。这种pull模式很适合Prometheus的工作方式,Prometheus会周期性地连接到应用来获取最新数据。

builder
    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation();
    })
    .AddPrometheusHttpListener();  // 导出到Prometheus

然后在Prometheus配置文件(prometheus.yml)中添加抓取任务:

scrape_configs:
  - job_name: 'aspire-app'
    static_configs:
      - targets: ['localhost:9184']  # Prometheus导出端口

在Grafana中,你可以基于Prometheus数据创建告警规则:

groups:
  - name: aspire_alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
        for: 5m
        annotations:
          summary: "High error rate detected"
          description: "Error rate is above 5%"
      
      - alert: HighLatency
        expr: histogram_quantile(0.99, http_request_duration) > 1000
        for: 5m
        annotations:
          summary: "High request latency detected"
          description: "P99 latency exceeds 1 second"
      
      - alert: ServiceUnhealthy
        expr: up{job="aspire-app"} == 0
        for: 1m
        annotations:
          summary: "Service is down"
          description: "Service has been unreachable for 1 minute"

对于基于健康检查的告警,你可以定期轮询健康检查端点并基于结果触发告警。这可以通过自定义的监控脚本实现。这个例子使用HttpClient定期向健康检查端点发送GET请求。while(true)循环不断重复,实现了一个长时间运行的监控任务。client.GetAsync会向"http://localhost:5000/healthz/ready"发送异步请求,获取响应对象。如果响应的IsSuccessStatusCode为false(即HTTP状态码不在200-299范围),说明应用处于非就绪状态,此时调用LogAlert记录告警信息。如果请求本身抛出异常(例如网络超时、连接拒绝),catch块会捕获异常并同样记录告警,包含异常消息便于诊断。最后await Task.Delay(TimeSpan.FromSeconds(30))使监控暂停30秒再进行下一次检查,避免过度频繁的请求。这种轮询方式虽然简单,但适合小规模应用或开发环境。在生产环境中,通常会使用更专业的监控系统来执行此类检查并触发告警。

using HttpClient client = new();
while (true)
{
    try
    {
        var response = await client.GetAsync("http://localhost:5000/healthz/ready");
        if (!response.IsSuccessStatusCode)
        {
            // 触发告警
            LogAlert("Service is not ready");
        }
    }
    catch (Exception ex)
    {
        // 触发告警
        LogAlert($"Health check failed: {ex.Message}");
    }
    
    await Task.Delay(TimeSpan.FromSeconds(30));
}

在生产环境中,Aspire建议使用OTLP导出遥测数据到专业的应用性能管理(APM)系统,如Azure Application Insights或其他支持OTLP的服务。这些系统通常提供更高级的告警、聚合和分析功能。

八、总结

.NET Aspire为分布式应用提供了完整的健康检查和监控解决方案。通过内置的健康检查机制,应用可以确保依赖关系正确建立并持续验证服务可用性。自定义健康检查允许你针对特定的业务需求进行细粒度的监控。

OpenTelemetry指标收集使得应用能够发送丰富的性能数据,而Aspire仪表板提供了强大的可视化界面来实时观察应用行为。追踪功能特别有价值,它允许你跟踪请求穿过整个微服务架构的路径,这对于性能优化和故障诊断至关重要。

在本地开发环境中,Aspire仪表板已经足够强大。但对于需要更高级功能(如自动告警、长期数据存储和趋势分析)的场景,可以将Aspire与Prometheus、Grafana或Azure Application Insights等专业监控工具集成。

最佳实践是在应用设计的早期阶段就考虑可观测性。定义有意义的健康检查和指标,使用一致的命名约定,并在应用的关键路径中记录遥测数据。这样不仅能在开发阶段快速定位问题,也为生产部署提供了坚实的监控基础。通过充分利用.NET Aspire的这些功能,你可以构建更可靠、更易维护的分布式应用。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐