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
的流量分配的例子,有两个Locality
X和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会根据下面这个步骤来进行负载均衡。
- 根据
Priority Level
选择合适的优先级 - 找到优先级后对这个优先级下的所有
Locality
执行Locality weighted load balancing
确定发往这些Locality
的流量比例。 - 对一个
Locality
包含的host使用指定的负载均衡算法获取要发送的host