Envoy源码分析之Load balancing Priority

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: # Priority Level 在上一篇文章中我提到`Priority`,一个集群可以包含多个`Priority`,每一个`Priority`下可以有很多主机,默认的`Priority`是0,也是最高的优先级,如果一个集群同时存在多个`Priority`,Envoy是如何来进行处理呢? Envoy默认只从`Priority`为0的的主机中进行负载均衡,直到`Priority`为0的主机其健

Priority Level

在上一篇文章中我提到Priority,一个集群可以包含多个Priority,每一个Priority下可以有很多主机,默认的Priority是0,也是最高的优先级,如果一个集群同时存在多个Priority,Envoy是如何来进行处理呢? Envoy默认只从Priority为0的的主机中进行负载均衡,直到Priority为0的主机其健康的比例低于某个阀值才会按照一定的比例将流量发往下一个优先级。那么问题来了? 如何界定主机是否健康?,这个阀值是多少?,发到下一个优先级的流量的比例是如何计算的? 接下来针对这些问题我们从代码中找寻答案。

// 一个Host的ymal表示
{
  "endpoint": "{...}",
  "endpoint_name": "...",
  "health_status": "...",
  "metadata": "{...}",
  "load_balancing_weight": "{...}"
}

每一个主机都会有个health_status,这个标志默认是UNKNOWN,Envoy认为带有这个标志的机器,其健康状态是Healthy的,相关的代码如下:

/**
 * Implementation of Upstream::Host.
 */
class HostImpl : public HostDescriptionImpl,
                 public Host,
                 public std::enable_shared_from_this<HostImpl> {
public:
  HostImpl(ClusterInfoConstSharedPtr cluster, const std::string& hostname,
           Network::Address::InstanceConstSharedPtr address,
           const envoy::api::v2::core::Metadata& metadata, uint32_t initial_weight,
           const envoy::api::v2::core::Locality& locality,
           const envoy::api::v2::endpoint::Endpoint::HealthCheckConfig& health_check_config,
           uint32_t priority, const envoy::api::v2::core::HealthStatus health_status)
      : HostDescriptionImpl(cluster, hostname, address, metadata, locality, health_check_config,
                            priority),
        used_(true) {
    // 初始化的时候设置根据health_status来设置health_flag
    setEdsHealthFlag(health_status);
    weight(initial_weight);
  }
    .......
  // 查询主机的健康状况
  Host::Health health() const override {
    // If any of the unhealthy flags are set, host is unhealthy.
    if (healthFlagGet(HealthFlag::FAILED_ACTIVE_HC) ||
        healthFlagGet(HealthFlag::FAILED_OUTLIER_CHECK) ||
        healthFlagGet(HealthFlag::FAILED_EDS_HEALTH)) {
      return Host::Health::Unhealthy;
    }

    // If any of the degraded flags are set, host is degraded.
    if (healthFlagGet(HealthFlag::DEGRADED_ACTIVE_HC) ||
        healthFlagGet(HealthFlag::DEGRADED_EDS_HEALTH)) {
      return Host::Health::Degraded;
    }
        // health_status的值为UNKNOW的时候,health_flags_为0,所以被认为是Healthy的
    // The host must have no flags or be pending removal.
    ASSERT(health_flags_ == 0 || healthFlagGet(HealthFlag::PENDING_DYNAMIC_REMOVAL));
    return Host::Health::Healthy;
  }

private:
    ......
  std::atomic<uint64_t> health_flags_{};
};
// health_status默认值是UNKNOW,所以health_flags_没有被设置,是默认值0。
void HostImpl::setEdsHealthFlag(envoy::api::v2::core::HealthStatus health_status) {
  switch (health_status) {
  case envoy::api::v2::core::HealthStatus::UNHEALTHY:
    FALLTHRU;
  case envoy::api::v2::core::HealthStatus::DRAINING:
    FALLTHRU;
  case envoy::api::v2::core::HealthStatus::TIMEOUT:
    healthFlagSet(Host::HealthFlag::FAILED_EDS_HEALTH);
    break;
  case envoy::api::v2::core::HealthStatus::DEGRADED:
    healthFlagSet(Host::HealthFlag::DEGRADED_EDS_HEALTH);
    break;
  default:;
    break;
    // No health flags should be set.
  }
}

在Envoy的运行过程中,这个状态可能会因为健康检查机制使其变成UNHEALTHY,也可以是控制面通过xDS下发EDS的时候,主动设置一个UNHEALTHY的status。接着我们来看下这个健康的阀值是多少?

100% == Overprovisioning Factor / 100.0 * 健康主机的比例

Envoy并没有选择直接使用健康主机的比例来确定阀值,而是通过设置Overprovisioning Factor(默认值140,可以通过xDS动态来改变),然后通过上面公式乘以健康主机的比例。并根据计算的结果是否小于100%来确定是否要将流量发往其他优先级。也就是说默认情况下,健康主机的比例低于72%就会导致结果小于100%,也就会将流量发往下一个优先级。那到底要发多少比例的流量呢?

P=0 healthy endpoints Traffic to P=0 Traffic to P=1
100% 100% 0%
72% 100% 0%
71% 99% 1%
50% 70% 30%
25% 35% 65%
0% 0% 100%

转发多少比例其实就是看上面公式的计算结果,结果是70%,那么就转发30%的流量到下一个优先级。如果我们发往下一个优先级的流量超过下一个优先级可能承载的比例这该怎么办?例如下面这张图(P表示优先级)

P=0 healthy endpoints P=1 healthy endpoints Traffic to P=0 Traffic to P=1
100% 100% 100% 0%
72% 72% 100% 0%
71% 71% 99% 1%
50% 50% 70% 30%
25% 100% 35% 65%
25% 25% 50% 50%

下一优先级的机器可以承载多少流量也是通过上面的公式计算出来的,如果发往下一个优先级的流量小于它可以承载的流量就全部发过去,否则就按照两个优先级通过上面公式计算出来的结果按比例来均摊。上图中的最后一组数据中,两个优先级的健康主机比例都是25%,根据公示计算出来的结果应该是(1.4 * 25 = 35),也就是承载35%的流量,然后将剩下的65%流量转发到P1,但是P1的健康主机的比例也是25%,没办法承受65%的流量,这个时候需要按照两者的所能承受的流量按比例来划分。

Degraded endpoints

讨论完优先级后,接下来我们来聊一下Envoy支持的另外一个特性,host降级,这类降级的host能够接收流量,但只有在没有足够的健康主机可用时才会接收流量。那么到底接收多少流量呢? 计算的方式和上面讨论的优先级一样。

P=0 healthy/degraded/unhealthy Traffic to P=0 healthy hosts Traffic to P=0 degraded hosts
100%/0%/0% 100% 0%
71%/0%/29% 100% 0%
71%/29%/0% 99% 1%
25%/65%/10% 35% 65%
5%/0%/95% 100% 0%

简单来说就是健康的主机先接收流量,处理不了的,交给降级的host来处理,仍然处理不了的,就交给下一个优先级来处理。那如何才能称为降级的host呢?,有两种方式,第一种就是通过控制面下发EDS的时候,将host的health_status字段设置为DEGRADED,另外一种就是配置健康检查,upstream的机器在收到健康检查的请求时,在response中带上x-envoy-degraded的header就表明这个主机被降级了。

Locality weighted load balancing

Envoy的Load balancer模块是很强大的,它支持区域相关的路由,比如区域感知路由、还有接下来要说的区域权重负载均衡。Envoy中的host都是带有Locality的,也就是有区域的属性。每一个Locality都会带有一个权重。当开启Locality weighted load balancing的时候,会按照权重比例将流量发到每一个Locality,每一个Locality根据指定的负载均衡算法来挑选主机。如果某个Locality中的主机无法承载那么多流量(根据 Overprovisioning Factor / 100.0 * 健康主机的比例这个公式计算出来,如果小于100%就无法承载),就会按照下列算法进行流量的分配。

// Setp1: 计算一个Locality X可以承载的流量
availability(L_X) = 140 * available_X_upstreams / total_X_upstreams

// Setp2: 计算Loocality X的有效权重等于实际权重乘以可以承载的流量比例 
effective_weight(L_X) = locality_weight_X * min(100, availability(L_X))

// Setp3: 计算接收的流量比例,locality X的有效权重除以所有的locality的有效权重之和
load to L_X = effective_weight(L_X) / Σ_c(effective_weight(L_c))

下面是一个Locality weighted load balancing的流量分配的例子,有两个LocalityX和Y,前者的权重是1,后者的权重是2。Y可以100%承载主机流量。

L=X healthy endpoints Percent of traffic to L=X Percent of traffic to L=Y
100% 33% 67%
70% 33% 67%
69% 32% 68%
50% 26% 74%
25% 15% 85%
0% 0% 100%

比如上面这张图中,当Locality X的健康主机比例是25%的时候,根据公式计算出来可以承受35%的流量,Locality X的权重是1,所以最后的有效权重是35%,locality Y可以承受100%的流量,其有效权重为100% * 2 = 200%,最后Locality X可以接收的流量比例等于35% / (200% + 35%) ~ 15%。 到此为止Locality weighted load balancing就讲解完毕了。

Panic threshold

最后来讲一下Envoy中的Panic mode的概念,正常模式下,我们认为配置的集群是有能力承载目前的流量,但是随着部分upstream因为健康检查等原因从可用的机器列表中移除时,会导致我们的可用的upstream数量减少。但是要承载的总的流量大小并没有改变,也就是说我们需要使用更少的机器来承载相同的流量,一旦可用的upstream低于某个阀值Envoy就认为达到了Panic mode,就会将流量发到所有的机器,无论是否可用。这个阀值就称为Panic threshold

总结

在前面的文章中谈到的负载均衡算法,以及本文中提到的Locality weighted load balancing、`Priority Level,这几者之间的关系到底是啥? 谁先谁后? 总的来说Envoy会根据下面这个步骤来进行负载均衡。

  1. 根据Priority Level选择合适的优先级
  2. 找到优先级后对这个优先级下的所有Locality执行Locality weighted load balancing确定发往这些Locality的流量比例。
  3. 对一个Locality包含的host使用指定的负载均衡算法获取要发送的host

参考文献

Next

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
4月前
|
负载均衡 算法 网络协议
gRPC Load Balancing
gRPC Load Balancing
58 5
|
12天前
|
存储 缓存 安全
在 Service Worker 中配置缓存策略
Service Worker 是一种可编程的网络代理,允许开发者控制网页如何加载资源。通过在 Service Worker 中配置缓存策略,可以优化应用性能,减少加载时间,提升用户体验。此策略涉及缓存的存储、更新和检索机制。
|
负载均衡 应用服务中间件 Nacos
最全解决:微服务之间调用出现Load balancer does not have available server for client
最全解决:微服务之间调用出现Load balancer does not have available server for client
1976 1
最全解决:微服务之间调用出现Load balancer does not have available server for client
|
域名解析 弹性计算 负载均衡
Application Load Balancer-ALB
简要介绍ALB由来及基础配置操作
|
负载均衡 算法 安全
Envoy源码分析之Load balancing基础
# 什么是Load balancing? > Load balancing is a way of distributing traffic between multiple hosts within a single upstream cluster in order to effectively make use of available resources. There are man
1364 0
|
存储 缓存 安全
Envoy源码分析之Stats Scope
# Scope 在上一篇文章中提到Envoy中通过`Scope`来创建`Metrics`,为什么要搞一个`Scope`的东西出来呢?`Scope`诞生的目的其实是为了更好的管理一组`stats`,比如关于集群的`stats`,这类`stats`的名称有个特点就是都是以`cluster.`作为前缀,那么可以以`cluster.`来创建一个`Scope`,这样就可以通过这个`Scope`来管理
1104 0
|
存储 网络协议 前端开发
Envoy源码分析之Stats基础
# 简介 Envoy官方文档中提到`One of the primary goals of Envoy is to make the network understandable`,让网络变的可理解,为了实现这个目标Envoy中内置了`stats`用于统计各类网络相关的指标,Envoy没有选择使用`Prometheus`SDK,而是选择自己实现了`stats`,[目的是为了适配Envoy的线
1629 0
Envoy源码分析之Stats基础
|
存储 NoSQL 调度
如何使用Redis让周期异步任务变得Fault-tolerant且Dynamic
        Python技术栈的同学一定都非常了解Celery——基于消息队列的分布式任务调度系统。(具体用法介绍不在此赘述)。通过Celery可以快速高效的将大规模的任务实时分发到众多的不同的机器上,让用户只关注每个单独任务的处理,而非调度分配任务本身。
1069 0
Envoy源码分析之Stats使用基础
# Stats基本使用 在上一篇文章中我们介绍了`Metrics`,以及对应的三个具体的`Metrics`类型`CounterImpl`、`GaugeImpl`、`HistogramImpl`,而本文将会介绍下,如何去使用这个三个Metrics类型。在Envoy中要定义一组`stats`一般会按照下面的步骤来创建。 1. 定义一组`stats` ```cpp #define M
1060 0