写在前面

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

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

源码研究

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

   1:  public void ConfigureServices(IServiceCollection services)
   2:  {
   3:      services.AddHealthChecks();
   4:  }
   5:   
   6:  public void Configure(IApplicationBuilder app)
   7:  {
   8:       app.UseRouting();
   9:   
  10:       app.UseEndpoints(endpoints =>
  11:        {
  12:            endpoints.MapHealthChecks("/health");
  13:        });
  14:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
其中services.AddHealthChecks();会把我们引入到HealthCheckService的扩展方法中,代码如下:

   1:  public static class HealthCheckServiceCollectionExtensions
   2:  {
   3:      public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
   4:      {
   5:          services.TryAddSingleton<HealthCheckService, DefaultHealthCheckService>();
   6:          services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, HealthCheckPublisherHostedService>());
   7:          return new HealthChecksBuilder(services);
   8:      }
   9:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

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

   1:  public abstract Task<HealthReport> CheckHealthAsync(
   2:              Func<HealthCheckRegistration, bool> predicate,
   3:              CancellationToken cancellationToken = default);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

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

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

   1:  public override async Task<HealthReport> CheckHealthAsync(
   2:      Func<HealthCheckRegistration, bool> predicate,
   3:      CancellationToken cancellationToken = default)
   4:  {
   5:      var registrations = _options.Value.Registrations;
   6:      if (predicate != null)
   7:      {
   8:          registrations = registrations.Where(predicate).ToArray();
   9:      }
  10:   
  11:      var totalTime = ValueStopwatch.StartNew();
  12:      Log.HealthCheckProcessingBegin(_logger);
  13:   
  14:      var tasks = new Task<HealthReportEntry>[registrations.Count];
  15:      var index = 0;
  16:      using (var scope = _scopeFactory.CreateScope())
  17:      {
  18:          foreach (var registration in registrations)
  19:          {
  20:              tasks[index++] = Task.Run(() => RunCheckAsync(scope, registration, cancellationToken), cancellationToken);
  21:          }
  22:   
  23:          await Task.WhenAll(tasks).ConfigureAwait(false);
  24:      }
  25:   
  26:      index = 0;
  27:      var entries = new Dictionary<string, HealthReportEntry>(StringComparer.OrdinalIgnoreCase);
  28:      foreach (var registration in registrations)
  29:      {
  30:          entries[registration.Name] = tasks[index++].Result;
  31:      }
  32:   
  33:      var totalElapsedTime = totalTime.GetElapsedTime();
  34:      var report = new HealthReport(entries, totalElapsedTime);
  35:      Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime);
  36:      return report;
  37:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

1、其内部有比较完善的监控机制,会在内部维护了一个Log功能,全程监控健康检查的耗时,该日志所记录的健康检查不仅仅是一个健康检查集合的耗时,而且也记录了每个Name的耗时。

2、该方法会通过await Task.WhenAll(tasks).ConfigureAwait(false);并发执行健康检查。当然,我需要注意的是,过多的健康检查任务将会导致系统性能的下降,这主要看如何取舍了

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

以下是RunCheckAsync方法的源码​:

   1:  private async Task<HealthReportEntry> RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration, CancellationToken cancellationToken)
   2:  {
   3:      cancellationToken.ThrowIfCancellationRequested();
   4:   
   5:      var healthCheck = registration.Factory(scope.ServiceProvider);
   6:   
   7:      using (_logger.BeginScope(new HealthCheckLogScope(registration.Name)))
   8:      {
   9:          var stopwatch = ValueStopwatch.StartNew();
  10:          var context = new HealthCheckContext { Registration = registration };
  11:   
  12:          Log.HealthCheckBegin(_logger, registration);
  13:   
  14:          HealthReportEntry entry;
  15:          CancellationTokenSource timeoutCancellationTokenSource = null;
  16:          try
  17:          {
  18:              HealthCheckResult result;
  19:   
  20:              var checkCancellationToken = cancellationToken;
  21:              if (registration.Timeout > TimeSpan.Zero)
  22:              {
  23:                  timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  24:                  timeoutCancellationTokenSource.CancelAfter(registration.Timeout);
  25:                  checkCancellationToken = timeoutCancellationTokenSource.Token;
  26:              }
  27:   
  28:              result = await healthCheck.CheckHealthAsync(context, checkCancellationToken).ConfigureAwait(false);
  29:   
  30:              var duration = stopwatch.GetElapsedTime();
  31:   
  32:              entry = new HealthReportEntry(
  33:                  status: result.Status,
  34:                  description: result.Description,
  35:                  duration: duration,
  36:                  exception: result.Exception,
  37:                  data: result.Data,
  38:                  tags: registration.Tags);
  39:   
  40:              Log.HealthCheckEnd(_logger, registration, entry, duration);
  41:              Log.HealthCheckData(_logger, registration, entry);
  42:          }
  43:          catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
  44:          {
  45:              var duration = stopwatch.GetElapsedTime();
  46:              entry = new HealthReportEntry(
  47:                  status: HealthStatus.Unhealthy,
  48:                  description: "A timeout occured while running check.",
  49:                  duration: duration,
  50:                  exception: ex,
  51:                  data: null);
  52:   
  53:              Log.HealthCheckError(_logger, registration, ex, duration);
  54:          }
  55:   
  56:          // Allow cancellation to propagate if it's not a timeout.
  57:          catch (Exception ex) when (ex as OperationCanceledException == null)
  58:          {
  59:              var duration = stopwatch.GetElapsedTime();
  60:              entry = new HealthReportEntry(
  61:                  status: HealthStatus.Unhealthy,
  62:                  description: ex.Message,
  63:                  duration: duration,
  64:                  exception: ex,
  65:                  data: null);
  66:   
  67:              Log.HealthCheckError(_logger, registration, ex, duration);
  68:          }
  69:   
  70:          finally
  71:          {
  72:              timeoutCancellationTokenSource?.Dispose();
  73:          }
  74:   
  75:          return entry;
  76:      }
  77:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

来自官方的应用

  • 数据库探测,例子可以是执行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

    .NET Core 3.1之深入源码理解HealthCheck(二)的更多相关文章

    1. .NET Core 3.0之深入源码理解Configuration(二)

        文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义 ...

    2. .NET Core 3.0之深入源码理解HttpClientFactory(二)

        写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...

    3. .NET Core 3.0之深入源码理解Host(二)

        写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...

    4. .NET Core 3.0之深入源码理解HealthCheck(一)

      写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...

    5. .NET Core 3.0之深入源码理解Startup的注册及运行

      原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

    6. .NET Core 3.0之深入源码理解Configuration(三)

        写在前面 上一篇文章讨论了文件型配置的基本内容,本篇内容讨论JSON型配置的实现方式,理解了这一种配置类型的实现方式,那么其他类型的配置实现方式基本可以触类旁通.看过了上一篇文章的朋友,应该看得出 ...

    7. .NET Core 3.0之深入源码理解Configuration(一)

      Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...

    8. .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)

        写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...

    9. .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)

        前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...

    随机推荐

    1. day3_python之函数返回值、语句形式、表达式形式

      一. 函数对象 1. 函数是第一类对象,即函数可以当作数据传递 #1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素 二.返回值 return的返回值没有类型 ...

    2. iptables 操作规则

      iptables -nL查看本机关于iptables的设置情况,默认查看的是-t filter,可以指定-t nat iptables-save > iptables.rule会保存当前的防火墙 ...

    3. 自然语言处理课程(二):Jieba分词的原理及实例操作

      上节课,我们学习了自然语言处理课程(一):自然语言处理在网文改编市场的应用,了解了相关的基础理论.接下来,我们将要了解一些具体的.可操作的技术方法. 作为小说爱好者的你,是否有设想过通过一些计算机工具 ...

    4. 阿里云ECS服务器活动99元一年,最高可买三年

      这几天阿里云 99一年.279三年的服务器活动如火如荼,和之前腾讯三年的服务器非常类似,非常低的价格换取非常高的价值,当然,通常情况下便宜没好货的,想要玩一下的老铁可以进阿里云去看看,阿里云270三年 ...

    5. Python--day19--random模块

      random模块 >>> import random #随机小数 >>> random.random() # 大于0且小于1之间的小数 0.766433866365 ...

    6. 前端开发之JavaScript

      JavaScript JS是一种脚本语言,浏览器执行,用于渲染HTML网页,实现网页的动画效果. JavaScript的引用方式: 1,在HTML文件中script标签中写JS代码 <scrip ...

    7. MFC/Win32里面调用qtwebkit

      可以用qtwinmigrate 文档在:http://doc.qt.digia.com/solutions/4/qtwinmigrate/index.html 下载在:https://qt.gitor ...

    8. H3C 更新发送全部路由表浪费网络资源

    9. python模块之序列化模块

      序列化 """ 序列--字符串 序列化--其他数据类型转化为字符串数据类型 反序列化--字符串转化为其他数据类型 """ json模块 &q ...

    10. Bishops Alliance—— 最大上升子序列

      原题链接:http://codeforces.com/gym/101147/problem/F 题意:n*n的棋盘,给m个主教的坐标及其私有距离p,以及常数C,求位于同一对角线上满足条件:dist(i ...