案例——从零开始搭建 ASP.NET Core 健康检查实例

发布于:2025-09-01 ⋅ 阅读:(29) ⋅ 点赞:(0)

1. 项目创建与基础设置

创建新项目

首先,创建一个新的 ASP.NET Core Web API 项目:

dotnet new webapi -n HealthCheckDemo
cd HealthCheckDemo

添加必要的 NuGet 包

添加健康检查相关的 NuGet 包:

dotnet add package Microsoft.AspNetCore.Diagnostics.HealthChecks
dotnet add package AspNetCore.HealthChecks.SqlServer  # 用于数据库健康检查
dotnet add package AspNetCore.HealthChecks.Redis     # 用于 Redis 健康检查

2. 健康检查原理解析

在深入代码之前,让我们先理解健康检查的核心概念:

  1. 健康检查服务:ASP.NET Core 提供了一个框架,用于报告应用程序及其依赖组件的健康状态
  2. 检查类型
    • 存活检查 (Liveness):应用程序是否正在运行
    • 就绪检查 (Readiness):应用程序是否准备好处理请求
    • 依赖检查:外部依赖(数据库、缓存等)是否可用
  3. 响应格式:健康检查端点返回一个 JSON 对象,包含整体状态和各个检查的详细结果

3. 完整代码实现

Program.cs 完整代码

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Text.Json;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);

// 添加服务到容器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// 1. 注册健康检查服务
builder.Services.AddHealthChecks()
    // 2. 添加一个简单的存活检查(总是健康)
    .AddCheck("self", () => HealthCheckResult.Healthy("Application is running"), tags: new[] { "live" })
    // 3. 添加数据库健康检查(模拟)
    .AddCheck("database", () => 
    {
        // 模拟数据库检查
        var isHealthy = CheckDatabaseConnection();
        return isHealthy 
            ? HealthCheckResult.Healthy("Database connection is OK") 
            : HealthCheckResult.Unhealthy("Database connection failed");
    }, tags: new[] { "ready", "database" })
    // 4. 添加 Redis 健康检查(模拟)
    .AddCheck("redis", () => 
    {
        // 模拟 Redis 检查
        var isHealthy = CheckRedisConnection();
        return isHealthy 
            ? HealthCheckResult.Healthy("Redis connection is OK") 
            : HealthCheckResult.Unhealthy("Redis connection failed");
    }, tags: new[] { "ready", "redis" });

// 模拟数据库连接检查方法
bool CheckDatabaseConnection()
{
    // 在实际应用中,这里会尝试连接到真实数据库
    // 这里我们模拟90%的成功率
    return new Random().NextDouble() > 0.1;
}

// 模拟 Redis 连接检查方法
bool CheckRedisConnection()
{
    // 在实际应用中,这里会尝试连接到真实 Redis
    // 这里我们模拟80%的成功率
    return new Random().NextDouble() > 0.2;
}

var app = builder.Build();

// 配置 HTTP 请求管道
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

// 5. 配置健康检查端点

// 综合健康检查端点(包含所有检查)
app.MapHealthChecks("/health", new HealthCheckOptions
{
    // 自定义响应格式
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        
        var response = new
        {
            status = report.Status.ToString(),
            totalDuration = report.TotalDuration.ToString(),
            checks = report.Entries.Select(e => new
            {
                name = e.Key,
                status = e.Value.Status.ToString(),
                description = e.Value.Description,
                duration = e.Value.Duration.ToString()
            })
        };
        
        await context.Response.WriteAsync(JsonSerializer.Serialize(response, 
            new JsonSerializerOptions { WriteIndented = true }));
    }
});

// 存活检查端点(只包含快速的基本检查)
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("live"),
    ResponseWriter = WriteHealthCheckResponse
});

// 就绪检查端点(包含所有依赖检查)
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready"),
    ResponseWriter = WriteHealthCheckResponse
});

// 健康检查响应写入器
async Task WriteHealthCheckResponse(HttpContext context, HealthReport report)
{
    context.Response.ContentType = "application/json";
    
    var result = JsonSerializer.Serialize(new
    {
        status = report.Status.ToString(),
        checks = report.Entries.Select(e => new
        {
            name = e.Key,
            status = e.Value.Status.ToString()
        })
    });
    
    await context.Response.WriteAsync(result);
}

app.Run();

添加一个示例控制器

创建 Controllers/TestController.cs

using Microsoft.AspNetCore.Mvc;

namespace HealthCheckDemo.Controllers;

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    private static int _requestCount = 0;
    
    [HttpGet]
    public IActionResult Get()
    {
        _requestCount++;
        
        // 每10个请求模拟一次故障
        if (_requestCount % 10 == 0)
        {
            return StatusCode(500, "Simulated server error");
        }
        
        return Ok($"Request #{_requestCount}: Hello from TestController!");
    }
}

4. 测试健康检查

启动应用程序

dotnet run

测试不同健康检查端点

  1. 综合健康检查(所有检查):

    curl -k https://localhost:7003/health
    
  2. 存活检查(只检查应用本身):

    curl -k https://localhost:7003/health/live
    
  3. 就绪检查(检查应用和所有依赖):

    curl -k https://localhost:7003/health/ready
    

示例响应

成功响应

{
  "status": "Healthy",
  "totalDuration": "00:00:00.1024567",
  "checks": [
    {
      "name": "self",
      "status": "Healthy",
      "description": "Application is running",
      "duration": "00:00:00.0000862"
    },
    {
      "name": "database",
      "status": "Healthy",
      "description": "Database connection is OK",
      "duration": "00:00:00.1001234"
    },
    {
      "name": "redis",
      "status": "Healthy",
      "description": "Redis connection is OK",
      "duration": "00:00:00.1002341"
    }
  ]
}

失败响应

{
  "status": "Unhealthy",
  "totalDuration": "00:00:00.2034567",
  "checks": [
    {
      "name": "self",
      "status": "Healthy",
      "description": "Application is running",
      "duration": "00:00:00.0000762"
    },
    {
      "name": "database",
      "status": "Unhealthy",
      "description": "Database connection failed",
      "duration": "00:00:00.2001234"
    },
    {
      "name": "redis",
      "status": "Healthy",
      "description": "Redis connection is OK",
      "duration": "00:00:00.2002341"
    }
  ]
}

5. 与容器编排系统集成

创建 Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["HealthCheckDemo.csproj", "."]
RUN dotnet restore "HealthCheckDemo.csproj"
COPY . .
RUN dotnet build "HealthCheckDemo.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "HealthCheckDemo.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HealthCheckDemo.dll"]

Kubernetes 部署配置

创建 deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: healthcheck-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: healthcheck-demo
  template:
    metadata:
      labels:
        app: healthcheck-demo
    spec:
      containers:
      - name: healthcheck-demo
        image: healthcheck-demo:latest
        ports:
        - containerPort: 80
        # 存活探针 - 检查应用是否正在运行
        livenessProbe:
          httpGet:
            path: /health/live
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        # 就绪探针 - 检查应用是否准备好接收流量
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 1
        # 启动探针 - 检查应用是否已启动
        startupProbe:
          httpGet:
            path: /health/live
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          failureThreshold: 30
---
apiVersion: v1
kind: Service
metadata:
  name: healthcheck-demo-service
spec:
  selector:
    app: healthcheck-demo
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer

6. 高级功能:健康检查 UI

添加健康检查 UI

dotnet add package AspNetCore.HealthChecks.UI
dotnet add package AspNetCore.HealthChecks.UI.Client
dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage

更新 Program.cs

builder.Services.AddHealthChecks() 后添加:

// 添加健康检查 UI 服务
builder.Services.AddHealthChecksUI(setup =>
{
    setup.AddHealthCheckEndpoint("API", "/health");
    setup.SetEvaluationTimeInSeconds(60); // 每60秒检查一次
    setup.SetMinimumSecondsBetweenFailureNotifications(60); // 失败通知最小间隔
})
.AddInMemoryStorage();

在端点映射部分添加:

// 健康检查 UI 端点
app.MapHealthChecksUI(setup => 
{
    setup.UIPath = "/healthchecks-ui";
    setup.ApiPath = "/healthchecks-api";
});

现在您可以访问 /healthchecks-ui 查看健康检查的可视化界面。

7. 实际数据库健康检查

替换模拟的数据库检查为真实的 SQL Server 检查:

// 在 Program.cs 的顶部添加
using Microsoft.Data.SqlClient;

// 替换模拟的数据库检查
.AddSqlServer(
    connectionString: builder.Configuration.GetConnectionString("DefaultConnection"),
    healthQuery: "SELECT 1;", // 简单的健康检查查询
    name: "sql",
    failureStatus: HealthStatus.Unhealthy,
    tags: new[] { "ready", "database" }
)

appsettings.json 中添加连接字符串:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=master;User Id=sa;Password=YourPassword123;TrustServerCertificate=true;"
  },
  // 其他配置...
}

总结

通过这个完整的实例,您已经学会了:

  1. 健康检查的基本概念:存活检查、就绪检查和依赖检查
  2. 如何注册健康检查服务:使用 AddHealthChecks()AddCheck() 方法
  3. 如何创建自定义健康检查:实现简单的检查逻辑
  4. 如何配置健康检查端点:使用 MapHealthChecks() 方法
  5. 如何自定义响应格式:使用 ResponseWriter 选项
  6. 如何与容器编排系统集成:配置 Kubernetes 探针
  7. 如何添加健康检查 UI:使用健康检查 UI 包

健康检查是构建可靠、可观测的分布式系统的关键组件,它可以帮助您及时发现和解决问题,确保应用程序的高可用性。


网站公告

今日签到

点亮在社区的每一天
去签到