.NET 7健康检查重复执行:如何实现单次执行兼顾存储与通知
问题解决:避免健康检查重复执行,同时实现结果持久化与故障邮件通知
问题分析
当前配置存在两个独立的定时触发逻辑,导致健康检查重复执行且功能割裂:
HealthCheckPublisherOptions.Period:.NET内置的健康检查定时任务,会重复执行检查并调用CustomHealthCheckPublisher.PublishAsync,但不会将结果持久化到PostgreSQLHealthChecksUI.SetEvaluationTimeInSeconds:HealthChecksUI的定时任务,会调用健康检查端点并将结果存入数据库,但不会触发自定义邮件通知
解决方案
核心思路是禁用.NET内置的健康检查定时任务,完全依托HealthChecksUI的定时机制执行检查并持久化结果,同时在UI执行完检查后触发自定义邮件通知逻辑。
具体步骤
- 移除内置定时配置:删除
HealthCheckPublisherOptions的Period设置,避免内置定时触发重复检查 - 绑定UI执行回调:在HealthChecksUI的配置中添加
OnHealthCheckExecuted回调,每次UI完成检查并持久化结果后,调用CustomHealthCheckPublisher.PublishAsync发送故障邮件
修改后的代码
Startup.cs
public void ConfigureServices(IServiceCollection services) { services .AddHealthChecks() .AddCheck<NotificationsServiceHealthCheck>("Notifications service health check", tags: new[] { "external-service" }); // 移除内置健康检查定时配置,避免重复执行 // services.Configure<HealthCheckPublisherOptions>(options => // { // options.Period = TimeSpan.FromSeconds(evaluationTimeInSeconds); // }); services.AddSingleton<IHealthCheckPublisher, CustomHealthCheckPublisher>(); var evaluationTimeInSeconds = 60; services .AddHealthChecksUI(setup => { setup.AddHealthCheckEndpoint("External services health checks", "https://localhost:44366/health-external-services"); setup.SetEvaluationTimeInSeconds(evaluationTimeInSeconds); // 添加UI执行完成后的回调,触发邮件通知 setup.OnHealthCheckExecuted(async (context) => { var publisher = context.ServiceProvider.GetRequiredService<CustomHealthCheckPublisher>(); await publisher.PublishAsync(context.Report, context.CancellationToken); }); }) .AddPostgreSqlStorage("CONNECTION_STRING", c => c.UseNpgsql(o => o.MigrationsHistoryTable(HistoryRepository.DefaultTableName, "hangfire"))); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger) { app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health-external-services", new HealthCheckOptions() { Predicate = r => r.Tags.Contains("external-service"), ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); endpoints.MapHealthChecksUI(setup => { setup.UIPath = "/HealthChecks"; setup.AsideMenuOpened = false; }); }); }
CustomHealthCheckPublisher.cs(无需修改)
public class CustomHealthCheckPublisher : IHealthCheckPublisher { public Task PublishAsync(HealthReport report, CancellationToken cancellationToken) { if (report.Status == HealthStatus.Healthy) { return Task.CompletedTask; } var nonhealthyChecks = report.Entries .Where(x => x.Value.Status != HealthStatus.Healthy) .ToList(); // 发送包含nonhealthyChecks的故障邮件 return Task.CompletedTask; } }
效果说明
- 健康检查仅由HealthChecksUI的定时任务触发,每60秒执行一次
- 检查结果自动持久化到PostgreSQL
- 每次检查完成后,自动调用
CustomHealthCheckPublisher.PublishAsync,仅当检查状态为非健康时发送邮件通知
内容的提问来源于stack exchange,提问作者Aleks Vujic
相关产品推荐
相关产品推荐

