ASP.NET Core 6框架揭秘实例演示[42]:检查应用的健康状况
现代化的应用及服务的部署场景主要体现在集群化、微服务和容器化,这一切都建立在针对部署应用或者服务的健康检查上。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]:检查应用的健康状况的更多相关文章
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
- ASP.NET Core 6框架揭秘实例演示[09]:配置绑定
我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...
- ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式
依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...
- ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式
在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...
- ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法
一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...
- ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]
<诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...
- ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法
为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...
- ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出
针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...
- ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用
.NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...
随机推荐
- 6.理解Jwt代码
1.昨天主要是对Jwt的代码里面不熟悉的代码进行了简要的理解,发现其实有些流程并不是普通程序员自己写的,而是自己进行拓展,这些流程是开源程序员写的开发流程:我发现这些代码一般要么是父类的方法实现,要么 ...
- 四月十号java知识点
1.数组:若干个相同数据类型元素按照一定顺序排列的集合2.JAVA语言内存分为栈内存和堆内存3.方法中的一些基本类型变量和对象的引用变量都在方法中的栈内存中分配4.堆内存用来存放new运算符创建的数组 ...
- Java 异常处理:使用和思考
概念 异常处理的概念起源于早期的编程语言,如 LISP.PL/I 和 CLU.这些编程语言首次引入了异常处理机制,以便在程序执行过程中检测和处理错误情况.异常处理机制随后在 Ada.Modula-3. ...
- Redis(二)redis发布与订阅以及三种新数据类型
1 配置文件 Utis单位部分 redis支持字节但不支持其他类型 Includes部分 设置包含的其他文件的目录 netword网络部分 bind:默认情况bind=127.0.0.1只接受本机的访 ...
- Job for nginx.service failed because the control process exited with error code.
1. nginx启动报错: Job for nginx.service failed because the control process exited with error code. See & ...
- 从 API 网关聊到 API 管理
在 API 管理中,通常会有这些痛点: 1.企业不清楚到底有多少个API,无法形成API资产管理等问题. 2.API在不同集群的生命周期问题. 3.API运行状态监控和告警问题. 4.API请求限流. ...
- 数据结构(DataStructure)-03
数据结构-03 **数据结构-03笔记** **递归** **二叉树** **广度遍历 - 二叉树** **深度遍历 - 二叉树** **二叉树练习一** **二叉树练习二** **二叉排序树练习一* ...
- scrapy框架简介
一.安装scrapy环境 -mac或linux:pip install scrapy -windows: 1.pip install wheel 2.pip install twinsted 3.pi ...
- Spring源码:Bean生命周期(三)
前言 在之前的文章中,我们已经对 bean 的准备工作进行了讲解,包括 bean 定义和 FactoryBean 判断等.在这个基础上,我们可以更加深入地理解 getBean 方法的实现逻辑,并在后续 ...
- oracle异常处理
序言 最近在工作中遇到这么一个场景: 在同一网段内存在着A库和B库,需要将A库下某些表的数据同步到B库 B库跑着定时任务,定时调用存储过程将A库下的数据同步到B库. B库和A库是通过建立dblink建 ...