首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ASP.NET核心健康检查:返回预评估结果

ASP.NET核心健康检查:返回预评估结果
EN

Stack Overflow用户
提问于 2020-10-07 14:15:50
回答 3查看 5.1K关注 0票数 6

我正在评估如何使用Microsoft健康检查来改进内部负载均衡器的路由。到目前为止,我对这个特性和它周围的社区提供的功能非常满意。然而,有一件事我还没有找到,我想问一问,这是否有可能是现成的:

一旦提出要求,健康检查似乎就会检索到自己的状态。但是,由于我们的服务在给定的时刻可能很难处理大量的请求,所以对SQL Server这样的第三方组件的查询可能需要很长的时间来响应。因此,我们希望定期对健康检查进行预评估(比如每隔几秒钟),并在调用健康检查api时返回该状态。

原因是,我们希望我们的负载平衡器尽快获得健康状态。对我们的用例来说,使用预评估的结果似乎足够好。

现在的问题是:是否可以在ASP.NET核心健康检查中添加一种“轮询”或“自动更新”机制?还是意味着我必须执行自己的健康检查,从定期对结果进行预评估的后台服务返回值?

请注意,我希望在每个请求上使用预先评估的结果,这不是HTTP缓存,在那里为下一个请求缓存活动结果。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-10-08 08:04:37

短版

这已经可用,并且已经可以与共同的监测系统相结合。您可以将健康检查直接绑定到您的监控基础结构中。

The details

Health中间件通过实现出版接口方法的任何注册类周期性地对目标进行IHealthCheckPublisher.PublishAsync度量。

代码语言:javascript
复制
services.AddSingleton<IHealthCheckPublisher, ReadinessPublisher>();

发布可以通过HealthCheckPublisherOptions进行配置。默认的时间段是30秒。这些选项可用于添加延迟、筛选要运行的检查等:

代码语言:javascript
复制
services.Configure<HealthCheckPublisherOptions>(options =>
{
    options.Delay = TimeSpan.FromSeconds(2);
    options.Predicate = (check) => check.Tags.Contains("ready");
});

一种选择是将结果( HealthReport实例)缓存到发布服务器,并从另一个HealthCheck端点服务它们。

也许更好的选择是将它们推送到像Application这样的监控系统,或者像Prometheus这样的时间序列数据库。AspNetCore.Diagnostics.HealthCheck软件包为App、Seq、Datadog和Prometheus提供了大量现成的支票和发行商。

普罗米修斯使用轮询本身。它定期调用所有已注册的源来检索度量。虽然这适用于服务,但它不适用于CLI应用程序。因此,应用程序可以将结果推送到Prometheus网关,该网关缓存度量,直到Prometheus自己提出请求。

代码语言:javascript
复制
services.AddHealthChecks()
        .AddSqlServer(connectionString: Configuration["Data:ConnectionStrings:Sample"])
        .AddCheck<RandomHealthCheck>("random")
        .AddPrometheusGatewayPublisher();

除了推进Prometheus之外,Prometheus 还提供了一个端点还通过AspNetcore.HealthChecks.Publisher.Prometheus包直接检索实时度量。其他应用程序可以使用相同的端点检索这些指标:

代码语言:javascript
复制
// default endpoint: /healthmetrics
app.UseHealthChecksPrometheusExporter();
票数 4
EN

Stack Overflow用户

发布于 2020-10-09 09:13:58

Panagiotis的答案很精彩,给我带来了一个优雅的解决方案,我很想留给下一个开发人员在这个问题上步履蹒跚。

为了在不实现后台服务或任何计时器的情况下实现定期更新,我注册了一个IHealthCheckPublisher。这样,ASP.NET核心将自动定期运行注册的健康检查,并将其结果发布到相应的实现中。

在我的测试中,健康报告在默认情况下每30秒发布一次。

代码语言:javascript
复制
// add a publisher to cache the latest health report
services.AddSingleton<IHealthCheckPublisher, HealthReportCachePublisher>();

我注册了我的实现HealthReportCachePublisher,它只做了一个发布的健康报告并将它保存在一个静态属性中。

我不太喜欢静态属性,但在我看来,它对这个用例来说已经足够了。

代码语言:javascript
复制
/// <summary>
/// This publisher takes a health report and keeps it as "Latest".
/// Other health checks or endpoints can reuse the latest health report to provide
/// health check APIs without having the checks executed on each request.
/// </summary>
public class HealthReportCachePublisher : IHealthCheckPublisher
{
    /// <summary>
    /// The latest health report which got published
    /// </summary>
    public static HealthReport Latest { get; set; }

    /// <summary>
    /// Publishes a provided report
    /// </summary>
    /// <param name="report">The result of executing a set of health checks</param>
    /// <param name="cancellationToken">A task which will complete when publishing is complete</param>
    /// <returns></returns>
    public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
    {
        Latest = report;
        return Task.CompletedTask;
    }
}

现在真正的魔法发生在这里

正如在每个健康检查示例中所看到的,我将健康检查映射到路由/health,并使用UIResponseWriter.WriteHealthCheckUIResponse返回一个漂亮的json响应。

但我绘制了另一条路线/health/latest。在那里,谓词_ => false完全阻止执行任何健康检查。但是,我没有返回零健康检查的空结果,而是通过访问静态HealthReportCachePublisher.Latest返回先前发布的健康报告。

代码语言:javascript
复制
app.UseEndpoints(endpoints =>
{
    // live health data: executes health checks for each request
    endpoints.MapHealthChecks("/health", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions()
    {
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });

    // latest health report: won't execute health checks but return the cached data from the HealthReportCachePublisher
    endpoints.MapHealthChecks("/health/latest", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions()
    {
        Predicate = _ => false, // do not execute any health checks, we just want to return the latest health report
        ResponseWriter = (context, _) => UIResponseWriter.WriteHealthCheckUIResponse(context, HealthReportCachePublisher.Latest)
    });
});

这样,调用/health就是通过对每个请求执行所有健康检查来返回实时健康报告。如果有许多事情需要检查或网络请求,这可能需要一段时间。

调用/health/latest将始终返回最新的预评估健康报告。这是非常快,如果您有一个负载平衡器等待健康报告路由传入的请求,可能会有很大帮助。

:上面的解决方案使用路由映射来取消健康检查的执行,并返回最新的健康状况报告。正如建议的那样,我试图首先构建一个进一步的健康检查,它应该返回最新的缓存的健康报告,但这有两个缺点:

  • 返回缓存报告本身的新健康检查也会出现在结果中(或者必须按名称或标记进行安装)。
  • 没有一种简单的方法可以将缓存的健康报告映射到HealthCheckResult。如果您复制属性和状态代码,这可能会工作。但由此产生的json基本上是一份包含内部健康报告的健康报告。这不是你想要的。
票数 7
EN

Stack Overflow用户

发布于 2022-02-10 22:36:29

另一种选择是使用洗涤器,并装饰HealthCheckService。如果您想要多管齐下地重新发布多个线程,那么您必须在从内部HealthCheckReport中获取HealthCheckService时添加一个锁定机制。这里就是一个很好的例子。

代码语言:javascript
复制
using System.Reflection;
using HealthCheckCache;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

// used by the Decorator CachingHealthCheckService
builder.Services.AddMemoryCache();
builder.Services.AddHttpContextAccessor();

// register all IHealthCheck types - basically builder.Services.AddTransient<AlwaysHealthy>(), but across all types in this assembly.
var healthServices = builder.Services.Scan(scan =>
    scan.FromCallingAssembly()
        .AddClasses(filter => filter.AssignableTo<IHealthCheck>())
        .AsSelf()
        .WithTransientLifetime()
);

// Register HealthCheckService, so it can be decorated.
var healthCheckBuilder = builder.Services.AddHealthChecks();
// Decorate the implementation with a cache
builder.Services.Decorate<HealthCheckService>((inner, provider) =>
    new CachingHealthCheckService(inner,
        provider.GetRequiredService<IHttpContextAccessor>(),
        provider.GetRequiredService<IMemoryCache>()
    )
);

// Register all the IHealthCheck instances in the container
// this has to be a for loop, b/c healthCheckBuilder.Add will modify the builder.Services - ServiceCollection
for (int i = 0; i < healthServices.Count; i++)
{
    ServiceDescriptor serviceDescriptor = healthServices[i];
    var isHealthCheck = serviceDescriptor.ServiceType.IsAssignableTo(typeof(IHealthCheck)) && serviceDescriptor.ServiceType == serviceDescriptor.ImplementationType;
    if (isHealthCheck)
    {
        healthCheckBuilder.Add(new HealthCheckRegistration(
            serviceDescriptor.ImplementationType.Name,
            s => (IHealthCheck)ActivatorUtilities.GetServiceOrCreateInstance(s, serviceDescriptor.ImplementationType),
            failureStatus: null,
            tags: null)
        );
    }

}

var app = builder.Build();

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

app.MapHealthChecks("/health", new HealthCheckOptions()
{
    AllowCachingResponses = true, // allow caching at Http level
});

app.Run();

public class CachingHealthCheckService : HealthCheckService
{
    private readonly HealthCheckService _innerHealthCheckService;
    private readonly IHttpContextAccessor _contextAccessor;
    private readonly IMemoryCache _cache;
    private const string CacheKey = "CachingHealthCheckService:HealthCheckReport";

    public CachingHealthCheckService(HealthCheckService innerHealthCheckService, IHttpContextAccessor contextAccessor, IMemoryCache cache)
    {
        _innerHealthCheckService = innerHealthCheckService;
        _contextAccessor = contextAccessor;
        _cache = cache;
    }

    public override async Task<HealthReport> CheckHealthAsync(Func<HealthCheckRegistration, bool>? predicate, CancellationToken cancellationToken = new CancellationToken())
    {
        HttpContext context = _contextAccessor.HttpContext;


        var forced = !string.IsNullOrEmpty(context.Request.Query["force"]);
        context.Response.Headers.Add("X-Health-Forced", forced.ToString());
        var cached = _cache.Get<HealthReport>(CacheKey);
        if (!forced && cached != null)
        {
            context.Response.Headers.Add("X-Health-Cached", "True");
            return cached;
        }
        var healthReport = await _innerHealthCheckService.CheckHealthAsync(predicate, cancellationToken);
        if (!forced)
        {
            _cache.Set(CacheKey, healthReport, TimeSpan.FromSeconds(30));
        }
        context.Response.Headers.Add("X-Health-Cached", "False");
        return healthReport;
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64245987

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档