我已在我的 asp.net core 应用程序中实现了健康检查。一个健康检查包括两个检查——DbContext 连接和自定义检查,检查 NpgsqlConnection。
99% 的情况下一切正常。偶尔会出现健康检查失败并抛出 TaskCanceledException 或 OperationCanceledException 异常。从我的日志中可以看到,这些异常在大约 2ms-25ms 后被抛出(因此没有任何超时的可能)。
重要提示:
当我多次点击健康检查(简单地在浏览器中按 F5 键)时,它会抛出异常。看起来在上一个健康检查完成之前,您不能点击 /health 端点。如果是这种情况,为什么呢?即使我在自定义健康检查中放置 Thread.Sleep(5000);(完全没有 DB 连接检查),如果我在 5 秒钟内点击 /health 端点,它也会失败。
问题:健康检查是否以某种“神奇”的方式单线程运行(当您再次点击该端点时,它会取消上一个健康检查调用)?
Startup.cs ConfigureServices
99% 的情况下一切正常。偶尔会出现健康检查失败并抛出 TaskCanceledException 或 OperationCanceledException 异常。从我的日志中可以看到,这些异常在大约 2ms-25ms 后被抛出(因此没有任何超时的可能)。
重要提示:
当我多次点击健康检查(简单地在浏览器中按 F5 键)时,它会抛出异常。看起来在上一个健康检查完成之前,您不能点击 /health 端点。如果是这种情况,为什么呢?即使我在自定义健康检查中放置 Thread.Sleep(5000);(完全没有 DB 连接检查),如果我在 5 秒钟内点击 /health 端点,它也会失败。
问题:健康检查是否以某种“神奇”的方式单线程运行(当您再次点击该端点时,它会取消上一个健康检查调用)?
Startup.cs ConfigureServices
services
.AddHealthChecks()
.AddCheck<StorageHealthCheck>("ReadOnly Persistance")
.AddDbContextCheck<MyDbContext>("EFCore persistance");
Startup.cs 配置
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseMiddleware<RequestLogMiddleware>();
app.UseMiddleware<ErrorLoggingMiddleware>();
if (!env.IsProduction())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "V1");
c.SwaggerEndpoint($"/swagger/v2/swagger.json", $"V2");
});
}
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = WriteResponse
});
app.UseMvc();
StorageHealthCheck.cs
public class StorageHealthCheck : IHealthCheck
{
private readonly IMediator _mediator;
public StorageHealthCheck(IMediator mediator)
{
_mediator = mediator;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{
var isReadOnlyHealthy = await _mediator.Send(new CheckReadOnlyPersistanceHealthQuery());
return new HealthCheckResult(isReadOnlyHealthy ? HealthStatus.Healthy : HealthStatus.Unhealthy, null);
}
}
CheckReadOnlyPersistanceHealthQueryHandler:
NpgsqlConnectionStringBuilder csb = new NpgsqlConnectionStringBuilder(_connectionString.Value);
string sql = $@"
SELECT * FROM pg_database WHERE datname = '{csb.Database}'";
try
{
using (IDbConnection connection = new NpgsqlConnection(_connectionString.Value))
{
connection.Open();
var stateAfterOpening = connection.State;
if (stateAfterOpening != ConnectionState.Open)
{
return false;
}
connection.Close();
return true;
}
}
catch
{
return false;
}
任务取消异常:
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at Npgsql.TaskExtensions.WithCancellation[T](Task`1 task, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnection.<>c__DisplayClass32_0.<<Open>g__OpenLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlDatabaseCreator.ExistsAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Diagnostics.HealthChecks.DbContextHealthCheck`1.CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken)
at Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService.CheckHealthAsync(Func`2 predicate, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckMiddleware.InvokeAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)
操作已取消异常:
System.OperationCanceledException: The operation was canceled.
at System.Threading.CancellationToken.ThrowOperationCanceledException()
at Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService.CheckHealthAsync(Func`2 predicate, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckMiddleware.InvokeAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)