前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET Core 3.1之深入源码理解HealthCheck(二)

.NET Core 3.1之深入源码理解HealthCheck(二)

作者头像
Edison.Ma
发布2020-05-18 15:14:46
8790
发布2020-05-18 15:14:46
举报
文章被收录于专栏:DotNet Core圈圈DotNet Core圈圈

写在前面

前文讨论了HealthCheck的理论部分,本文将讨论有关HealthCheck的应用内容。

  • 可以监视内存、磁盘和其他物理服务器资源的使用情况来了解是否处于正常状态。
  • 运行状况检查可以测试应用的依赖项(如数据库和外部服务终结点)以确认是否可用和正常工作。
  • 运行状况探测可以由容器业务流程协调程序和负载均衡器用于检查应用的状态。

源码研究

在应用中引入HealthCheck,一般需要配置Startup文件,如下所示:

代码语言:javascript
复制
public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks();
}

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/health");
    });
}

其中services.AddHealthChecks();会把我们引入到HealthCheckService的扩展方法中,代码如下:

代码语言:javascript
复制
public static class HealthCheckServiceCollectionExtensions
{
    public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
    {
        services.TryAddSingleton<HealthCheckService, DefaultHealthCheckService>();
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, HealthCheckPublisherHostedService>());
        return new HealthChecksBuilder(services);
    }
}

该扩展方法会尝试注册一个HealthCheckService的单例对象。HealthCheckService本身是一个抽象类,它内部含有一个抽象方法,主要用于执行健康检查并返回健康状态的聚合信息。抽象方法如下所示:

代码语言:javascript
复制
public abstract Task<HealthReport> CheckHealthAsync(
        Func<HealthCheckRegistration, bool> predicate,
        CancellationToken cancellationToken = default);

HealthCheckService有一个默认派生类,就是DefaultHealthCheckService,在其构造方法中,会去验证是否有重复的健康检查名称存在,如果有,就会抛出异常。另外名称的检查是不区分大小写的。该类所实现的抽象方法作为健康检查的核心功能,内部实现还是比较复杂的。

首先我们看一下该方法的实现源码:

代码语言:javascript
复制
public override async Task<HealthReport> CheckHealthAsync(
    Func<HealthCheckRegistration, bool> predicate,
    CancellationToken cancellationToken = default)
{
    var registrations = _options.Value.Registrations;
    if (predicate != null)
    {
        registrations = registrations.Where(predicate).ToArray();
    }

    var totalTime = ValueStopwatch.StartNew();
    Log.HealthCheckProcessingBegin(_logger);

    var tasks = new Task<HealthReportEntry>[registrations.Count];
    var index = 0;
    using (var scope = _scopeFactory.CreateScope())
    {
        foreach (var registration in registrations)
        {
            tasks[index++] = Task.Run(() => RunCheckAsync(scope, registration, cancellationToken), cancellationToken);
        }

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }

    index = 0;
    var entries = new Dictionary<string, HealthReportEntry>(StringComparer.OrdinalIgnoreCase);
    foreach (var registration in registrations)
    {
        entries[registration.Name] = tasks[index++].Result;
    }

    var totalElapsedTime = totalTime.GetElapsedTime();
    var report = new HealthReport(entries, totalElapsedTime);
    Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime);
    return report;
}

1、其内部有比较完善的监控机制,会在内部维护了一个Log功能,全程监控健康检查的耗时,该日志所记录的健康检查不仅仅是一个健康检查集合的耗时,而且也记录了每个Name的耗时。 2、该方法会通过await Task.WhenAll(tasks).ConfigureAwait(false);并发执行健康检查。当然,我需要注意的是,过多的健康检查任务将会导致系统性能的下降,这主要看如何取舍了

CheckHealthAsync内部还会调用一个私有方法RunCheckAsync,这是真正执行健康检查的方法。RunCheckAsync方法执行完成后,会创建HealthReportEntry对象返回到CheckHealthAsync中,并组装到HealthReport对象中,到此该抽象方法执行完毕。

以下是RunCheckAsync方法的源码:

代码语言:javascript
复制
private async Task<HealthReportEntry> RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration, CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();

    var healthCheck = registration.Factory(scope.ServiceProvider);

    using (_logger.BeginScope(new HealthCheckLogScope(registration.Name)))
    {
        var stopwatch = ValueStopwatch.StartNew();
        var context = new HealthCheckContext { Registration = registration };

        Log.HealthCheckBegin(_logger, registration);

        HealthReportEntry entry;
        CancellationTokenSource timeoutCancellationTokenSource = null;
        try
        {
            HealthCheckResult result;

            var checkCancellationToken = cancellationToken;
            if (registration.Timeout > TimeSpan.Zero)
            {
                timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                timeoutCancellationTokenSource.CancelAfter(registration.Timeout);
                checkCancellationToken = timeoutCancellationTokenSource.Token;
            }

            result = await healthCheck.CheckHealthAsync(context, checkCancellationToken).ConfigureAwait(false);

            var duration = stopwatch.GetElapsedTime();

            entry = new HealthReportEntry(
                status: result.Status,
                description: result.Description,
                duration: duration,
                exception: result.Exception,
                data: result.Data,
                tags: registration.Tags);

            Log.HealthCheckEnd(_logger, registration, entry, duration);
            Log.HealthCheckData(_logger, registration, entry);
        }
        catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
        {
            var duration = stopwatch.GetElapsedTime();
            entry = new HealthReportEntry(
                status: HealthStatus.Unhealthy,
                description: "A timeout occured while running check.",
                duration: duration,
                exception: ex,
                data: null);

            Log.HealthCheckError(_logger, registration, ex, duration);
        }

        // Allow cancellation to propagate if it's not a timeout.
        catch (Exception ex) when (ex as OperationCanceledException == null)
        {
            var duration = stopwatch.GetElapsedTime();
            entry = new HealthReportEntry(
                status: HealthStatus.Unhealthy,
                description: ex.Message,
                duration: duration,
                exception: ex,
                data: null);

            Log.HealthCheckError(_logger, registration, ex, duration);
        }

        finally
        {
            timeoutCancellationTokenSource?.Dispose();
        }

        return entry;
    }
}

来自官方的应用

  • 数据库探测,例子可以是执行select 1 from tableName根据数据库响应来判断是否健康
  • Entity Framework Core DbContext 探测,DbContext 检查确认应用可以与为 EF Core DbContext 配置的数据库通信。
  • 单独的就绪情况和运行情况探测,在某些托管方案中,可能初始化是一个比较耗时的操作,应用正常运行,但是可能还不能正常处理请求并响应
  • 具有自定义响应编写器的基于指标的探测,比如检查内存占用是否超标,cpu 是否占用过高,连接数是否达到上限
  • 按端口筛选,指定端口,一般用于容器环境,根据容器启动时配置的端口号进行响应
  • 分发运行状况检查库,将检查接口实现独立一个类,并通过依赖注入获取参数,检查时根据参数编写逻辑
  • 运行状况检查发布服务器,如果向 DI 添加 IHealthCheckPublisher,则运行状态检查系统将定期执行状态检查,并使用结果调用 PublishAsync。适用于需要推送的健康系统,而不是健康系统
  • 使用 MapWhen 限制运行状况检查,使用 MapWhen 对运行状况检查终结点的请求管道进行条件分支

其他更多内容请参考:https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet技术平台 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 源码研究
  • 来自官方的应用
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档