现代化的应用及服务的部署场景主要体现在集群化、微服务和容器化,这一切都建立在针对部署应用或者服务的健康检查上。ASP.NET提供的健康检查不仅可能确定目标应用或者服务的可用性,还具有健康报告发布功能。ASP.NET框架的健康检查功能是通过HealthCheckMiddleware中间件完成的。我们不仅可以利用该中间件确定当前应用的可用性,还可以注册相应的IHealthCheck对象来完成针对不同方面的健康检查。(本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)

[S3001]确定应用可用状态(源代码

[S3002]定制健康检查逻辑(源代码

[S3003]改变健康状态对应的响应状态码(源代码

[S3004]提供细粒度的健康检查(源代码

[S3005]定制健康报告响应内容(源代码

[S3006]IHealthCheck对象的过滤(源代码

[S3007]定期发布健康报告(源代码

[S3001]确定应用可用状态

对于部署于集群或者容器的应用或者服务来说,它需要对外暴露一个终结点,负载均衡器或者容器编排框架以一定的频率向该终结点发送“心跳”请求,以确定应用和服务的可用性。演示程序应用采用如下的方式提供了这个健康检查终结点。

var builder = WebApplication.CreateBuilder();
builder.Services.AddHealthChecks();
var app = builder.Build();
app.UseHealthChecks(path: "/healthcheck");
app.Run();

演示程序调用了UseHealthChecks扩展方法注册了HealthCheckMiddleware中间件,并利用指定的参数将健康检查终结点的路径设置为“/healthcheck”。该中间件依赖的服务通过调用AddHealthChecks扩展方法进行注册。在程序正常运行的情况下,如果利用浏览器向注册的健康检查路径“/healthcheck”发送一个简单的GET请求,就可以得到图1所示的“健康状态”。

图1 健康检查结果

如下所示的代码片段是健康检查响应报文的内容。这是一个状态码为“200 OK”且媒体类型为“text/plain”的响应,其主体内容就是健康状态的字符串描述。在大部分情况下,发送健康检查请求希望得到的是目标应用或者服务当前实时的健康状况,所以响应报文是不应该被缓存的,如下所示的响应报文的“Cache-Control”和“Pragma”报头也体现了这一点。

HTTP/1.1 200 OK
Content-Type: text/plain
Date: Sat, 13 Nov 2021 05:08:00 GMT
Server: Kestrel
Cache-Control: no-store, no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Length: 7 Healthy

[S3002]定制健康检查逻辑

对于前面演示的实例来说,只要应用正常启动,它就被视为“健康”(完全可用),这种情况有时候可能并不是我们希望的。有时候应用在启动之后需要做一些初始化的工作,并希望在这些工作完成之前当前应用处于不可用的状态,这样请求就不会被导流进来。这样的需求就需要我们自行实现具体的健康检查逻辑。下面的演示程序将健康检查实现在内嵌的Check方法中,该方法会随机返回三种健康状态(Healthy、Unhealthy和Degraded)。在调用AddHealthChecks扩展方法注册所需依赖服务并返回IHealthChecksBuilder对象后,它接着调用了该对象的AddCheck方法注册了一个IHealthCheck对象,后者会调用Check方法决定当前的健康状态。

using Microsoft.Extensions.Diagnostics.HealthChecks;

var random = new Random();
var builder = WebApplication.CreateBuilder();
builder.Services
.AddHealthChecks()
.AddCheck(name:"default",check: Check);
var app = builder.Build();
app.UseHealthChecks(path: "/healthcheck");
app.Run(); HealthCheckResult Check() => random!.Next(1, 4) switch
{
1 => HealthCheckResult.Unhealthy(),
2 => HealthCheckResult.Degraded(),
_ => HealthCheckResult.Healthy(),
};

如下所示的代码片段是针对三种健康状态的响应报文,可以看出它们的状态码是不同的。针对健康状态Healthy和Degraded,响应码都是“200 OK”,因为此时的应用或者服务均会被视为可用(Available)状态,两者之间只是“完全可用”和“部分可用”的区别。状态为Unhealthy的服务被视为不可用(Unavailable),所以响应状态码为“503 Service Unavailable”。

HTTP/1.1 200 OK
Content-Type: text/plain
Date: Sat, 13 Nov 2021 05:08:00 GMT
Server: Kestrel
Cache-Control: no-store, no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Length: 7 Healthy
HTTP/1.1 503 Service Unavailable
Content-Type: text/plain
Date: Sat, 13 Nov 2021 05:13:42 GMT
Server: Kestrel
Cache-Control: no-store, no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Length: 9 Unhealthy
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Sat, 13 Nov 2021 05:14:05 GMT
Server: Kestrel
Cache-Control: no-store, no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Length: 8 Degraded

[S3003]改变健康状态对应的响应状态码

前面我们已经简单解释了三种健康状态与对应的响应状态码。虽然健康检查默认响应状态码的设置是合理的,但是不能通过状态码来区分Healthy和Unhealthy这两种可用状态,可以通过如下所示的方式来改变默认的响应状态码设置。

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks; var random = new Random();
var options = new HealthCheckOptions
{
ResultStatusCodes = new Dictionary<HealthStatus, int>
{
[HealthStatus.Healthy] = 299,
[HealthStatus.Degraded] = 298,
[HealthStatus.Unhealthy] = 503
}
}; var builder = WebApplication.CreateBuilder();
builder.Services
.AddHealthChecks()
.AddCheck(name:"default",check: Check);
var app = builder.Build();
app.UseHealthChecks(path: "/healthcheck", options: options);
app.Run(); HealthCheckResult Check() => random!.Next(1, 4) switch
{
1 => HealthCheckResult.Unhealthy(),
2 => HealthCheckResult.Degraded(),
_ => HealthCheckResult.Healthy(),
};

上面的演示程序调用UseHealthChecks扩展方法注册HealthCheckMiddleware中间件时提供了一个HealthCheckOptions配置选项。此配置选项通过ResultStatusCodes属性返回的字典维护了这三种健康状态与对应响应状态码之间的映射关系。演示程序将针对Healthy和Unhealthy这两种健康状态对应的响应状态码分别设置为“299”与“298”,它们体现在如下所示的三种响应报文中。

HTTP/1.1 299
Content-Type: text/plain
Date: Sat, 13 Nov 2021 05:19:34 GMT
Server: Kestrel
Cache-Control: no-store, no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Length: 7 Healthy
HTTP/1.1 298
Content-Type: text/plain
Date: Sat, 13 Nov 2021 05:19:30 GMT
Server: Kestrel
Cache-Control: no-store, no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Length: 8 Degraded

[S3004]提供细粒度的健康检查

如果当前应用承载或者依赖了若干组件或者服务,我们可以针对它们做细粒度的健康检查。前面的演示实例通过注册的IHealthCheck对象对“应用级别”的健康检查进行了定制,我们可以采用同样的形式为某个组件或者服务注册相应的IHealthCheck对象来确定它们的健康状况。

using Microsoft.Extensions.Diagnostics.HealthChecks;

var random = new Random();
var builder = WebApplication.CreateBuilder();
builder.Services.AddHealthChecks()
.AddCheck(name: "foo", check: Check)
.AddCheck(name: "bar", check: Check)
.AddCheck(name: "baz", check: Check);
var app = builder.Build();
app.UseHealthChecks(path: "/healthcheck");
app.Run(); HealthCheckResult Check() => random!.Next(1, 4) switch
{
1 => HealthCheckResult.Unhealthy(),
2 => HealthCheckResult.Degraded(),
_ => HealthCheckResult.Healthy(),
};

假设当前应用承载了三个服务,分别命名为foo、bar和baz,我们可以采用如下所示的方式为它们注册三个IHealthCheck对象来完成针对它们的健康检查。由于注册的三个IHealthCheck对象采用同一个Check方法决定最后的健康状态,所以最终具有27种不同的组合。针对三个服务的27种健康状态组合最终会产生如下三种不同的响应报文。

HTTP/1.1 200 OK
Date: Sat, 13 Nov 2021 05:20:30 GMT
Content-Type: text/plain
Server: Kestrel
Cache-Control: no-store, no-cache
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 7 Healthy
HTTP/1.1 200 OK
Date: Sat, 13 Nov 2021 05:21:30 GMT
Content-Type: text/plain
Server: Kestrel
Cache-Control: no-store, no-cache
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 8 Degraded
HTTP/1.1 503 Service Unavailable
Date: Sat, 13 Nov 2021 05:22:23 GMT
Content-Type: text/plain
Server: Kestrel
Cache-Control: no-store, no-cache
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 9 Unhealthy

健康检查响应并没有返回针对具体三个服务的健康状态,而是返回针对整个应用的整体健康状态,这个状态是根据三个服务当前的健康状态组合计算出来的。按照严重程度,三种健康状态的顺序应该是Unhealthy > Degraded > Healthy,组合中最严重的健康状态就是应用整体的健康状态。按照这个逻辑,如果应用的整体健康状态为Healthy,就意味着三个服务的健康状态都是Healthy;如果应用的整体健康状态为Degraded,就意味着至少有一个服务的健康状态为Degraded,并且没有Unhealthy;如果其中某个服务的健康状态为Unhealthy,应用的整体健康状态就是Unhealthy。

[S3005]定制健康报告响应内容

上面演示的实例虽然注册了相应的IHealthCheck对象来检验独立服务的健康状况,但是最终得到的依然是应用的整体健康状态,我们更希望得到一份详细的针对所有服务的“健康诊断书”。所以,我们将演示程序做了如下所示的改写。我们为Check方法返回的表示健康检查结果的HealthCheckResult对象设置了对应的描述性文字(Normal、Degraded和Unavailable)。我们在调用AddCheck方法时指定了两个标签(Tag),如针对服务foo的IHealthCheck对象的标签设置为foo1和foo2。在调用UseHealthChecks扩展方法注册HealthCheckMiddleware中间件时,我们提供了HealthCheckOptions配置选项,通过之后后者的ResponseWriter属性完成了健康报告的呈现。

...
var options = new HealthCheckOptions
{
ResponseWriter = ReportAsync
}; var builder = WebApplication.CreateBuilder();
builder.Services.AddHealthChecks()
.AddCheck(name: "foo", check: Check,tags: new string[] { "foo1", "foo2" })
.AddCheck(name: "bar", check: Check, tags: new string[] { "bar1", "bar2" })
.AddCheck(name: "baz", check: Check, tags: new string[] { "baz1", "baz2" }); var app = builder.Build();
app.UseHealthChecks(path: "/healthcheck", options: options);
app.Run(); static Task ReportAsync(HttpContext context, HealthReport report)
{
context.Response.ContentType = "application/json";
var options = new JsonSerializerOptions();
options.WriteIndented = true;
options.Converters.Add(new JsonStringEnumConverter());
return context.Response.WriteAsync(JsonSerializer.Serialize(report, options));
}
...

HealthCheckOptions配置选项的ResponseWriter属性返回一个Func<HttpContext, HealthReport, Task>委托,显示的健康报告通过HealthReport对象标识。提供委托指向的ReportAsync会直接将指定的HealthReport对象序列化成JSON格式并作为响应的主体内容。我们并没有设置相应的状态码,所以可以直接在浏览器中看到图2所示的这份完整的健康报告。

图2 完整的健康报告

[S3006]IHealthCheck对象的过滤

HealthCheckMiddleware中间件提取注册的IHealthCheck对象在完成具体的健康检查工作之前,我们可以对它们做进一步过滤。前面演示的实例注册的IHealthCheck对象指定了相应的标签,该标签不仅会出现在健康报告中,我们可以使用它们作为过滤条件。如下的演示程序通过设置HealthCheckOptions配置选项的Predicate属性使之选择Tag前缀不为“baz”的IHealthCheck对象。

...
var options = new HealthCheckOptions
{
ResponseWriter = ReportAsync,
Predicate = reg => reg.Tags.Any(tag => !tag.StartsWith("baz", StringComparison.OrdinalIgnoreCase))
}; ...

由于我们设置的过滤规则相当于忽略了针对服务baz的健康检查,所以如图3所示的健康报告时就看不到对应的健康状态。

图3 部分IHealthCheck过滤后的健康报告

[S3007]定期发布健康报告

健康报告的发布是通过IHealthCheckPublisher服务来完成的,我们演示的程序定义了如下这个实现了该接口的ConsolePublisher类型,它会将健康报告输出到控制台上。

using Microsoft.Extensions.Diagnostics.HealthChecks;

var random = new Random();
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.Services
.AddHealthChecks()
.AddCheck("foo", Check)
.AddCheck("bar", Check)
.AddCheck("baz", Check)
.AddConsolePublisher()
.ConfigurePublisher(options =>options.Period = TimeSpan.FromSeconds(5));
var app = builder.Build();
app.UseHealthChecks(path: "/healthcheck");
app.Run();
HealthCheckResult Check() => random!.Next(1, 4) switch
{
1 => HealthCheckResult.Unhealthy(),
2 => HealthCheckResult.Degraded(),
_ => HealthCheckResult.Healthy(),
};

上面的演示程序注册了三个DelegateHealthCheck对象,它们会随机返回针对三种状态的健康状态。ConsolePublisher通过自定义的AddConsolePublisher扩展方法进行注册,紧随其后调用的ConfigurePublisher方法也是自定义的扩展方法,我们利用它将健康报告发布间隔设置为5秒。程序运行之后,当前应用的健康报告会以图4所示的形式输出到控制台上。

图4 健康报告的定期发布

ASP.NET Core 6框架揭秘实例演示[42]:检查应用的健康状况的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  2. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  3. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  4. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  5. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  6. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  7. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  8. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  9. ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出

    针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...

  10. ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用

    .NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...

随机推荐

  1. Ficow 的 AI 平台快速上手指南(ChatGPT, NewBing, ChatGLM-6B, cursor.so)

    本文首发于 Ficow Shen's Blog,原文地址: Ficow 的 AI 平台快速上手指南(ChatGPT, NewBing, ChatGLM-6B, cursor.so). 内容概览 前言 ...

  2. 五月十四号java基础知识点

    class Person{ private String name; private int age; public Person(String name,int age){ this.name = ...

  3. 扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理

    大家好,我是三友~~ 前几天有个大兄弟问了我一个问题,注册中心要集成SpringCloud,想实现SpringCloud的负载均衡,需要实现哪些接口和规范. 既然这个兄弟问到我了,而我又刚好知道,这不 ...

  4. day91:luffy:基于vue+drf的路飞学城项目后端部署

    目录 1.安装mysql镜像 2.把本地的数据导入到容器的mysql数据库中 3.安装redis容器 4.把后端项目部署前的处理 5.修改项目的配置文件:prod.py 6.从后端项目中收集静态文件 ...

  5. 从零开始TP6配置ThinkPHP-ApiDoc

    系统:windows11 集成环境:小皮(原phpstudy) composer:2.5 准备工作:安装小皮后,在软件管理中安装composer,2.3安装不上去,只能安装1.8.5,没关系安装后升级 ...

  6. vue中watch的详细用法(深度侦听)

    vsCode插件 在vue中,使用watch来响应数据的变化.watch的用法大致有三种.下面代码是watch的一种简单的用法: <input type="text" v-m ...

  7. Vue的生命周期的详解

    Vue的生命周期   Vue的生命周期是每个使用Vue框架的前端人员都需要掌握的知识,以此作为记录.   Vue的生命周期就是vue实例从创建到销毁的全过程,也就是new Vue() 开始就是vue生 ...

  8. Burnside 引理及其扩展

    之前学 Burnside 一直没能深入本质,这回与 QYB 学弟讨论了一下 Burnside 引理的证明,做一个记录. 前置知识:群的定义. 一.等价染色方案计数问题 对于一种染色方案组成的集合 \( ...

  9. 2023-04-06:拥抱Golang,优化FFmpeg音频编码器,探究encode_audio.c的内部结构。

    2023-04-06:拥抱Golang,优化FFmpeg音频编码器,探究encode_audio.c的内部结构. 答案2023-04-06: 见moonfdd/ffmpeg-go库. 这段代码是一个示 ...

  10. 2023-03-29:如何高效计算三条线路选择方案?小A的旅行线路规划问题

    2023-03-29:第一行有一个正整数n(3<=n<=100000),代表小A拟定的路线数量 第二行有n个正整数,第i个代表第i条路线的起始日期 第三行有n个正整数,第i个代表第i条路线 ...