YARP 目标运行状况检查

在大多数实际系统中,其节点偶尔会遇到暂时性问题,并完全由于各种原因(例如过载、资源泄漏、硬件故障等)而出现故障。理想情况下,最好完全防止这些不幸事件以主动方式发生,但设计和构建这种理想系统的成本一般高。 但是,还有另一种更便宜的反应方法,旨在最大程度地减少由于失败而导致的负面影响对客户端请求的影响。 代理可以分析每个节点的运行状况,并停止将客户端流量发送到不正常的节点,直到它们恢复为止。 YARP 以主动和被动目标地址健康检查的形式实现该方法。 它们彼此独立,并存储在每个目标的相对属性上。 运行状况状态使用 Unknown 值进行初始化,稍后可以通过相应的策略更改为 HealthyUnhealthy,如下所示。

主动健康检查

YARP 可以通过向指定的健康终结点发送定期探查请求并分析响应的结果,主动监视目标服务器的健康状况。 该分析根据为集群指定的主动健康检查策略执行,并计算出新的目标健康状态。 最后,策略根据 HTTP 响应代码(2xx 被视为正常)将每个目标标记为正常或不正常,并重新生成群集的正常目标集合。

有几个整个群集的配置设置用于控制主动健康检查,这些配置可以在配置文件或代码中设置。 为每个目的地指定专用的健康终端。

文件示例

"Clusters": {
  "cluster1": {
    "HealthCheck": {
      "Active": {
        "Enabled": "true",
        "Interval": "00:00:10",
        "Timeout": "00:00:10",
        "Policy": "ConsecutiveFailures",
        "Path": "/api/health",
        "Query": "?foo=bar"
      }
    },
    "Metadata": {
      "ConsecutiveFailuresHealthPolicy.Threshold": "3"
    },
    "Destinations": {
      "cluster1/destination1": {
        "Address": "https://localhost:10000/"
      },
      "cluster1/destination2": {
        "Address": "http://localhost:10010/",
        "Health": "http://localhost:10020/"
      }
    }
  }
}

代码示例

var clusters = new[]
{
    new ClusterConfig()
    {
        ClusterId = "cluster1",
        HealthCheck = new HealthCheckConfig
        {
            Active = new ActiveHealthCheckConfig
            {
                Enabled = true,
                Interval = TimeSpan.FromSeconds(10),
                Timeout = TimeSpan.FromSeconds(10),
                Policy = HealthCheckConstants.ActivePolicy.ConsecutiveFailures,
                Path = "/api/health",
                Query = "?foo=bar",
            }
        },
        Metadata = new Dictionary<string, string> { { ConsecutiveFailuresHealthPolicyOptions.ThresholdMetadataName, "5" } },
        Destinations =
        {
            { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } },
            { "destination2", new DestinationConfig() { Address = "https://localhost:10010", Health = "https://localhost:10010" } }
        }
    }
};

配置

Cluster/HealthCheck/Active 部分中的群集级别指定了除一个以外的所有主动运行状况检查设置。 唯一的例外是一个可选的 Destination/Health 元素,该元素指定单独的主动健康检查终结点。 实际运行状况探测 URI 的结构为 Destination/Address(所设置的 Destination/Health)+ Cluster/HealthCheck/Active/Path

主动运行状况检查设置还可以通过反映配置约定的 Yarp.ReverseProxy.Configuration 命名空间中的相应类型在代码中定义。

Cluster/HealthCheck/Active 节和 ActiveHealthCheckConfig

  • Enabled:指示是否为群集启用主动运行状况检查的标志。 默认 false
  • Interval:发送健康探测请求的间隔。 默认 00:00:15
  • Timeout:探测请求超时。 默认 00:00:10
  • Policy:用于评估目标的主动运行状况状态的策略名称。 必需参数
  • Path:所有群集目标上的运行状况检查路径。 默认 null
  • Query:所有群集目标上的运行状况检查查询。 默认 null

Destination 节和 DestinationConfig

  • Health - 专用运行状况探测终结点,例如 http://destination:12345/。 默认 null,然后回退到 Destination/Address

内置策略

目前有一个内置的主动运行状况检查策略 - ConsecutiveFailuresHealthPolicy。 它会对连续健康探测失败进行计数,一旦达到所设定的阈值后将目标标记为不健康。 在第一次成功响应后,目标被标记为健康,计数器将被重置。 策略参数在群集的元数据中设置,如下所示:

ConsecutiveFailuresHealthPolicy.Threshold - 将目标标记为运行不正常所需达到的主动运行状况探测请求连续失败次数。 默认 2

设计

此进程中的主要服务是 IActiveHealthCheckMonitor,它定期通过 IProbingRequestFactory创建探测请求,并将其发送到启用了活动运行状况检查的每个 ClusterConfig 的所有 DestinationConfig,然后将所有响应传递到为群集指定的 IActiveHealthCheckPolicyIActiveHealthCheckMonitor 不会就目标是否正常做出实际决定,而是由为集群指定的 IActiveHealthCheckPolicy 来负责这一职责。 调用策略以在所有群集的目标探测完成后评估新的运行状况状态。 它接受 ClusterState 表示群集的动态状态和一组 DestinationProbingResult 用于存储群集目标的探测结果。 评估了每个目的地的新健康状态后,策略会调用 IDestinationHealthUpdater 来真正更新 DestinationHealthState.Active 值。

-{For each cluster's destination}-
IActiveHealthCheckMonitor <--(Create probing request)--> IProbingRequestFactory
        |
        V
 HttpMessageInvoker <--(Send probe and receive response)--> Destination
        |
(Save probing result)
        |
        V
DestinationProbingResult
--------------{END}---------------
        |
(Evaluate new destination active health states using probing results)
        |
        V
IActiveHealthCheckPolicy --(New active health states)--> IDestinationHealthUpdater --(Update each destination's)--> DestinationState.Health.Active

上述所有组件都有默认的内置实现,必要时也可以替换为自定义组件。

可扩展性

活动健康检查子系统中有 2 个主要扩展点。

IActiveHealthCheckPolicy

IActiveHealthCheckPolicy 分析目标如何响应 IActiveHealthCheckMonitor发送的活动运行状况探测,评估所有探测目标的新活动运行状况状态,然后调用 IDestinationHealthUpdater.SetActive 来设置新的活动运行状况状态,并根据更新的值重新生成正常的目标集合。

下面是自定义目标标记 IActiveHealthCheckPolicy 的简单示例,如果探测返回成功的响应代码,则目标标记为 Healthy,否则为 Unhealthy

public class FirstUnsuccessfulResponseHealthPolicy : IActiveHealthCheckPolicy
{
    private readonly IDestinationHealthUpdater _healthUpdater;

    public FirstUnsuccessfulResponseHealthPolicy(IDestinationHealthUpdater healthUpdater)
    {
        _healthUpdater = healthUpdater;
    }

    public string Name => "FirstUnsuccessfulResponse";

    public void ProbingCompleted(ClusterState cluster, IReadOnlyList<DestinationProbingResult> probingResults)
    {
        if (probingResults.Count == 0)
        {
            return;
        }

        var newHealthStates = new NewActiveDestinationHealth[probingResults.Count];
        for (var i = 0; i < probingResults.Count; i++)
        {
            var response = probingResults[i].Response;
            var newHealth = response is not null && response.IsSuccessStatusCode ? DestinationHealth.Healthy : DestinationHealth.Unhealthy;
            newHealthStates[i] = new NewActiveDestinationHealth(probingResults[i].Destination, newHealth);
        }

        _healthUpdater.SetActive(cluster, newHealthStates);
    }
}

IProbingRequestFactory

IProbingRequestFactory 创建将发送到目标运行状况终结点的主动运行状况探测请求。 它可以考虑 ActiveHealthCheckOptions.PathDestinationConfig.Health和其他配置设置来构造探测请求。

默认 IProbingRequestFactory 使用与代理请求相同的 HttpRequest 配置。要进行自定义,可以实现自己的 IProbingRequestFactory,并将其注册到 DI 中,如下所示。

services.AddSingleton<IProbingRequestFactory, CustomProbingRequestFactory>();

下面是一个简单的示例,用户 IProbingRequestFactoryDestinationConfig.Address 和一个固定的健康探测路径串联起来,以创建探测请求 URI。

public class CustomProbingRequestFactory : IProbingRequestFactory
{
    public HttpRequestMessage CreateRequest(ClusterConfig clusterConfig, DestinationConfig destinationConfig)
    {
        var probeUri = new Uri(destinationConfig.Address + "/api/probe-health");
        return new HttpRequestMessage(HttpMethod.Get, probeUri) { Version = ProtocolHelper.Http11Version };
    }
}

被动式健康检查

YARP 可以被动监视客户端请求代理中的成功和失败,以响应性评估目标运行状况。 对代理请求的响应被专用被动运行状况检查中间件截获,该中间件将其传递给群集上配置的策略。 该政策分析响应,以评估产生它们的目的地是否健康。 然后,它会计算新的被动运行状况状态并将其分配给相应的目标,并重新生成群集的正常目标集合。

请注意,响应通常在被动运行状况策略运行之前发送到客户端,因此策略无法截获响应正文,也无法修改响应标头中的任何内容,除非代理应用程序引入了完整的响应缓冲。

与主动运行状况检查逻辑有一个重要区别。 一旦某个目标被指定为不健康的被动状态,它就会停止接收所有新流量,从而阻碍未来的健康状况重新评估。 该策略还计划在配置的期限后重新激活目标。 重新激活意味着将被动运行状况状态从 Unhealthy 重置回初始 Unknown 值,从而使目标再次符合流量条件。

有几个群集范围的配置设置控制被动运行状况检查,可以在配置文件或代码中设置。

文件示例

"Clusters": {
  "cluster1": {
    "HealthCheck": {
      "Passive": {
        "Enabled": "true",
        "Policy": "TransportFailureRate",
        "ReactivationPeriod": "00:02:00"
      }
    },
    "Metadata": {
      "TransportFailureRateHealthPolicy.RateLimit": "0.5"
    },
    "Destinations": {
      "cluster1/destination1": {
        "Address": "https://localhost:10000/"
      },
      "cluster1/destination2": {
        "Address": "http://localhost:10010/"
      }
    }
  }
}

代码示例

var clusters = new[]
{
    new ClusterConfig()
    {
        ClusterId = "cluster1",
        HealthCheck = new HealthCheckConfig
        {
            Passive = new PassiveHealthCheckConfig
            {
                Enabled = true,
                Policy = HealthCheckConstants.PassivePolicy.TransportFailureRate,
                ReactivationPeriod = TimeSpan.FromMinutes(2)
            }
        },
        Metadata = new Dictionary<string, string> { { TransportFailureRateHealthPolicyOptions.FailureRateLimitMetadataName, "0.5" } },
        Destinations =
        {
            { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } },
            { "destination2", new DestinationConfig() { Address = "https://localhost:10010" } }
        }
    }
};

配置

为群集级别指定的被动健康检查设置位于 Cluster/HealthCheck/Passive 节中。 或者,可以通过反映配置约定的 Yarp.ReverseProxy.Configuration 命名空间中的相应类型在代码中定义它们。

要让被动运行状况检查正常运行,需要将 PassiveHealthCheckMiddleware 添加到管道中。 默认 MapReverseProxy(this IEndpointRouteBuilder endpoints) 方法会自动执行该作,但在手动管道构造的情况下,必须调用 UsePassiveHealthChecks 方法以添加该中间件,如以下示例所示。

endpoints.MapReverseProxy(proxyPipeline =>
{
    proxyPipeline.UseAffinitizedDestinationLookup();
    proxyPipeline.UseProxyLoadBalancing();
    proxyPipeline.UseRequestAffinitizer();
    proxyPipeline.UsePassiveHealthChecks();
});

Cluster/HealthCheck/Passive 节和 PassiveHealthCheckConfig

  • Enabled - 指示是否为群集启用被动运行状况检查的标志。 默认 false
  • Policy - 评估目的地被动健康状态的策略的名称。 必需参数
  • ReactivationPeriod - 经过一段设定的时间后,状态异常的目标服务器的被动健康状态将重置为 Unknown,并开始重新接收流量。 默认值为 null,这意味着时间段将由 IPassiveHealthCheckPolicy 设置。

内置策略

目前有一个内置的被动运行状况检查策略 - TransportFailureRateHealthPolicy。 它计算每个目标的代理请求失败率,并在超出指定的限制时将其标记为不正常。 在给定的时间段内,失败请求占代理到目标的请求总数的比例被计算为一个百分比。 失败计数器和总数计数器在滑动时间窗口中被跟踪,这意味着只考虑窗口中的最近读数。 全局定义两组策略参数,每个群集级别都有两组策略参数。

全局参数通过选项机制设置,TransportFailureRateHealthPolicyOptions 类型具有以下属性:

  • DetectionWindowSize - 在速率计算中保留并考虑检测到的故障的时间段。 默认值为 00:01:00
  • MinimalTotalCountThreshold - 在该策略开始评估目标的运行状况并强制执行失败率限制之前,必须代理到检测窗口内目标的最少请求总数。 默认值为 10
  • DefaultFailureRateLimit - 未在群集元数据上设置时应用的标记为运行不正常的目标的默认故障率限制。 该值在 (0,1)范围内。 默认值为 0.3(30%)。

可在代码中设置全局策略选项,如下所示:

services.Configure<TransportFailureRateHealthPolicyOptions>(o =>
{
    o.DetectionWindowSize = TimeSpan.FromSeconds(30);
    o.MinimalTotalCountThreshold = 5;
    o.DefaultFailureRateLimit = 0.5;
});

群集特定的参数设置在群集的元数据中,如下所示:TransportFailureRateHealthPolicy.RateLimit - 将目标标记为不健康的故障率限制。 该值在 (0,1)范围内。 默认值由全局 DefaultFailureRateLimit 参数提供。

设计

主组件 PassiveHealthCheckMiddleware 位于请求管道中,并分析目标返回的响应。 对于属于已启用被动运行状况检查的群集的目标中的每个响应,PassiveHealthCheckMiddleware 会调用为该群集指定的 IPassiveHealthCheckPolicy。 该策略分析给定响应,评估新目标的被动运行状况状态,并调用 IDestinationHealthUpdater,以实际更新 DestinationHealthState.Passive 值。 更新在后台异步发生,不会阻止请求管道。 当目标被标记为运行不正常时,它会停止接收新请求,直到在配置的时间段之后被重新激活为止。 重新激活意味着目标 DestinationHealthState.Passive 状态从 Unhealthy 重置为 Unknown,并重新生成群集的正常目标列表以包含它。 在将目标的 DestinationHealthState.Passive 设置为 Unhealthy 后,IDestinationHealthUpdater 立即会计划重新激活。

      (Response to a proxied request)
                  |
      PassiveHealthCheckMiddleware
                  |
                  V
      IPassiveHealthCheckPolicy
                  |
    (Evaluate new passive health state)
                  |
      IDestinationHealthUpdater --(Asynchronously update passive state)--> DestinationState.Health.Passive
                  |
                  V
      (Schedule a reactivation) --(Set to Unknown)--> DestinationState.Health.Passive

可扩展性

被动运行状况检查子系统中有一个主要扩展点,即 IPassiveHealthCheckPolicy

IPassiveHealthCheckPolicy

IPassiveHealthCheckPolicy 分析目标如何响应代理客户端请求、评估其新的被动运行状况状态,最后调用 IDestinationHealthUpdater.SetPassiveAsync 以创建实际更新被动运行状况状态和重新生成正常目标集合的异步任务。

下面是首次响应代理请求失败时将目标标记为 Unhealthy 的自定义 IPassiveHealthCheckPolicy 的简单示例。

public class FirstUnsuccessfulResponseHealthPolicy : IPassiveHealthCheckPolicy
{
    private static readonly TimeSpan _defaultReactivationPeriod = TimeSpan.FromSeconds(60);
    private readonly IDestinationHealthUpdater _healthUpdater;

    public FirstUnsuccessfulResponseHealthPolicy(IDestinationHealthUpdater healthUpdater)
    {
        _healthUpdater = healthUpdater;
    }

    public string Name => "FirstUnsuccessfulResponse";

    public void RequestProxied(HttpContext context, ClusterState cluster, DestinationState destination)
    {
        var error = context.Features.Get<IForwarderErrorFeature>();
        if (error is not null)
        {
            var reactivationPeriod = cluster.Model.Config.HealthCheck?.Passive?.ReactivationPeriod ?? _defaultReactivationPeriod;
            _healthUpdater.SetPassive(cluster, destination, DestinationHealth.Unhealthy, reactivationPeriod);
        }
    }
}

可用目标集合

目标的运行状况状态用于判定哪些目标符合接收代理请求的条件。 每个群集在 AvailableDestinations 类型的 属性上维护其自己的可用目标列表。 当任何目标的运行状况状态发生更改时,该列表将重新生成。 IClusterDestinationsUpdater 控制该过程,并调用在群集上配置的 IAvailableDestinationsPolicy,以便从群集的所有目标中实际选择可用目标。 提供以下内置策略,并在必要时实现自定义策略。

  • HealthyAndUnknown - 检查每个 DestinationState 语句并将其添加到可用目标列表中(如果以下所有语句均为 TRUE)。 如果没有可用的目标,则请求将收到 503 错误。
    • 在群集或 DestinationHealthState.Active != DestinationHealth.Unhealthy 上禁用了主动运行状况检查
    • 在群集或 DestinationHealthState.Passive != DestinationHealth.Unhealthy 上禁用了被动运行状况检查
  • HealthyOrPanic - 首先调用 HealthyAndUnknown 策略以获取可用目标。 如果此调用未返回任何目标,则会将该群集的所有目标标记为可用。 这是默认策略。

注意:无论给定群集上是否启用了任何运行状况检查,都会始终调用群集上配置的可用目标策略。 禁用的运行状况检查的运行状况状态已设置为 Unknown

配置

文件示例

"Clusters": {
  "cluster1": {    
    "HealthCheck": {
      "AvailableDestinationsPolicy": "HealthyOrPanic",
      "Passive": {
        "Enabled": "true"
      }
    },
    "Destinations": {
      "cluster1/destination1": {
        "Address": "https://localhost:10000/"
      },
      "cluster1/destination2": {
        "Address": "http://localhost:10010/"
      }
    }
  }
}

代码示例

var clusters = new[]
{
    new ClusterConfig()
    {
        ClusterId = "cluster1",
        HealthCheck = new HealthCheckConfig
        {
            AvailableDestinationsPolicy = HealthCheckConstants.AvailableDestinations.HealthyOrPanic,
            Passive = new PassiveHealthCheckConfig
            {
                Enabled = true
            }
        },
        Destinations =
        {
            { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } },
            { "destination2", new DestinationConfig() { Address = "https://localhost:10010" } }
        }
    }
};