网站首页 网站源码
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using Dpz.Core.Public.ViewModel.RequestEvent;
using Dpz.Core.Public.ViewModel.Response;
using ProtocolType = Dpz.Core.EnumLibrary.ProtocolType;
namespace Dpz.Core.Service.Event;
public class HealthCheckHandler(
IHealthCheckService healthCheckService,
IHttpClientFactory httpClientFactory
) : IRequestHandler<HealthCheckRequest>
{
private readonly HttpClient _httpClient = httpClientFactory.CreateClient("edge");
private static Lazy<List<HealthCheckResponse>>? _lazyHealthChecks;
public async Task Handle(HealthCheckRequest request, CancellationToken cancellationToken)
{
if (_lazyHealthChecks is not { IsValueCreated: true })
{
var healthChecks = await healthCheckService.GetHealthChecksAsync();
_lazyHealthChecks = new Lazy<List<HealthCheckResponse>>(() => healthChecks);
}
foreach (var item in _lazyHealthChecks.Value)
{
var currentInterval = DateTime.Now - item.LastCheckTime;
if (currentInterval >= item.Interval)
{
var result = await CheckAsync(item);
item.LastCheckDuration = result.LastCheckDuration;
item.LastCheckTime = result.LastCheckTime;
}
}
}
private async Task<HealthCheckResultResponse> CheckAsync(HealthCheckResponse healthCheck)
{
var checkTimer = Stopwatch.StartNew();
var retries = 0;
Exception? lastError = null;
while (retries < healthCheck.RetryCount)
{
try
{
using var cts = new CancellationTokenSource(healthCheck.Timeout);
var isHealthy = await CheckEndpointAsync(healthCheck, cts.Token);
return new HealthCheckResultResponse()
{
Status = isHealthy ? HealthStatus.Healthy : HealthStatus.Unhealthy,
LastCheckDuration = checkTimer.Elapsed,
LastCheckTime = DateTime.Now,
ErrorMessage = lastError?.Message,
};
}
catch (Exception ex)
{
lastError = ex;
retries++;
// 重试间隔
await Task.Delay(1000);
}
}
return new HealthCheckResultResponse()
{
Status = HealthStatus.Unhealthy,
LastCheckDuration = checkTimer.Elapsed,
LastCheckTime = DateTime.Now,
ErrorMessage = lastError?.Message,
};
}
private async Task<bool> CheckEndpointAsync(
HealthCheckResponse healthCheck,
CancellationToken cancellationToken
)
{
return healthCheck.Protocol switch
{
ProtocolType.Http or ProtocolType.Https => await CheckHttpAsync(
healthCheck,
cancellationToken
),
ProtocolType.Tcp => CheckTcp(healthCheck),
_ => throw new NotSupportedException($"协议 {healthCheck.Protocol} 不支持"),
};
}
private async Task<bool> CheckHttpAsync(
HealthCheckResponse healthCheck,
CancellationToken cancellationToken
)
{
var response = await _httpClient.GetAsync(healthCheck.Endpoint, cancellationToken);
return response.IsSuccessStatusCode;
}
private bool CheckTcp(HealthCheckResponse healthCheck)
{
var parts = healthCheck.Endpoint.Split(':');
using var tcpClient = new TcpClient();
return tcpClient.ConnectAsync(parts[0], int.Parse(parts[1])).Wait(healthCheck.Timeout);
}
}
上述代码实现了一个健康检查处理程序 HealthCheckHandler
,用于定期检查一组服务的健康状态。它使用了依赖注入的方式来获取健康检查服务和 HTTP 客户端工厂。以下是代码的主要功能和结构的详细解释:
健康检查请求处理:
HealthCheckHandler
类实现了 IRequestHandler<HealthCheckRequest>
接口,负责处理健康检查请求。Handle
方法中,它会检查每个健康检查项的上次检查时间与当前时间的间隔,如果超过了设定的检查间隔,则会执行健康检查。懒加载健康检查列表:
_lazyHealthChecks
是一个 Lazy<List<HealthCheckResponse>>
类型的字段,用于懒加载健康检查列表。只有在第一次访问时才会从 healthCheckService
中获取健康检查项。健康检查逻辑:
CheckAsync
方法会被调用。该方法会尝试多次检查服务的健康状态,直到达到最大重试次数。Healthy
,否则返回 Unhealthy
,并记录最后的检查持续时间和错误信息。支持多种协议:
CheckEndpointAsync
方法根据健康检查项的协议类型(HTTP/HTTPS 或 TCP)调用相应的检查方法。CheckHttpAsync
方法发送 GET 请求并检查响应状态。CheckTcp
方法尝试连接到指定的主机和端口。异常处理与重试机制:
CheckAsync
方法中,使用了重试机制来处理可能的异常。如果在重试次数内仍然失败,则返回 Unhealthy
状态。构造函数:
IHealthCheckService
和 IHttpClientFactory
的实例,用于获取健康检查数据和创建 HTTP 客户端。Handle 方法:
CheckAsync 方法:
CheckEndpointAsync 方法:
CheckHttpAsync 和 CheckTcp 方法:
整体上,这段代码实现了一个灵活的健康检查机制,能够定期检查多个服务的健康状态,并支持 HTTP 和 TCP 协议。它通过懒加载和重试机制提高了性能和可靠性,适合用于微服务架构中的服务监控。