负载均衡#

Angie 会将客户端请求分配到由 upstream 块描述的一组上游服务器中。具体由哪台服务器处理某个请求,取决于该组的负载均衡方法。

本指南涵盖 HTTP 负载均衡,其配置位于 HTTP Upstream 模块中。TCP 和 UDP 会话的负载均衡方式相同,配置位于 Stream Upstream 模块中;有影响的差异会在相应位置说明。

一个服务器组只能使用一种负载均衡方法。如果块中指定了多个方法,则以最后一个为准,Angie 会记录日志 load balancing method redefined;如果块中未指定任何方法,则使用 加权轮询。

选择方法#

方法决定 Angie 如何为每个请求选择服务器,每种方法接受各自的一组 server 参数。方法不支持的参数不会被忽略:Angie 会拒绝启动,并报告 balancing method does not support parameter。在将某种方法与 weight、slow_start 或 backup 组合使用之前,请先查看下表。

方法

请求分配依据

级别

weight

slow_start

backup

加权轮询

按加权轮询中的位置分配。默认方法,无需指令。

L4、L7

hash

对你选择的键(例如 URI 或某个请求头)计算哈希。

L4、L7

ip_hash

对客户端地址计算哈希。

L7

least_conn

每单位权重的活动连接数最少者优先。

L4、L7

random

按权重比例随机选中。

L4、L7

least_time

选中几率与平均响应时间成反比。

L4、L7

feedback (PRO)

选中几率与服务器上报的值成正比。

L4、L7

除需要 Angie PRO 的 feedback 外,以上方法在 Angie 中均可用。ip_hash 没有对应的 Stream 方法;请改用带 $remote_addr 的 hash。

负载均衡方法#

加权轮询#

轮询是默认方法:只要服务器组未指定其他方法,Angie 就会使用它,它没有专属指令。Angie 依次轮流选择组内的服务器,每台服务器在轮转中所占的份额与其 weight 成正比:

upstream backend {
    zone backend 1m;
    server backend1.example.com weight=5;
    server backend2.example.com;
    server backend3.example.com;
}

每 7 个请求中有 5 个发往 backend1.example.com,其余两台服务器各收到 1 个。这种分配是平滑的:权重较高的服务器的请求会分散在整个轮询周期中,而不是集中在一次突发中发送,因此该组永远不会出现连续 5 个请求发往同一台服务器、随后 2 个发往其余服务器的情况。

轮询同时也是其他方法的后备方案。当 hash、ip_hash 或 random 无法确定一台可用服务器时,会把选择权交还给轮询。

hash#

hash 方法会将键的哈希值相同的所有请求发往同一台服务器。当组内服务器自行缓存响应时(例如缓存代理或 memcached 节点),这很有用。只要组成员保持不变,对某个 URI 的请求就总是到达同一台服务器,因此其响应只缓存在这一台服务器上,而不是缓存在组内的每台服务器上。

upstream backend {
    zone backend 1m;
    hash $request_uri consistent;
    server backend1.example.com;
    server backend2.example.com;
}

键可以是任意 变量 组成的字符串。权重的作用是让权重更高的服务器按比例占据更多的键空间。

未指定 consistent 时,Angie 会把键的哈希值映射到一个长度等于组内总权重的区间上,每台服务器占据其中长度等于自身权重的一段。添加或移除服务器会改变这一总权重,因此会重新映射许多并非分配给被添加或被移除服务器的键:它们的缓存响应留在了不再接收这些键请求的服务器上。指定 consistent 后,Angie 会改为将服务器放置在 ketama 环上,此时移除一台服务器只会重新映射该服务器所持有的键。

备注

服务器在环上的位置根据 server 指令中写出的名称计算,而不是根据该名称解析出的地址。同一名称的所有地址共用这些位置,因此该名称在环上占据的份额,与相同权重的单台服务器相当。当某个键落在该名称的某个位置上时,Angie 会通过轮询在其地址中选择一个。

未指定 consistent 时,每个地址都被视为一台单独的服务器,各自带有该权重,哈希值会映射到所有地址的总权重上。因此,解析出多个地址的名称所获得的份额,与这些地址的数量成正比。

ip_hash#

ip_hash 方法以客户端地址作为键:IPv4 地址取前三个八位组,IPv6 地址取完整地址。只要目标服务器可用,来自同一客户端的请求就会一直发往同一台服务器。

它没有一致性哈希模式,因此更改服务器集合可能会把许多客户端重定向到其他服务器。若要将某台服务器下线,同时保留其余映射,请将其标记为 down,而不要直接删除该行:

upstream backend {
    zone backend 1m;
    ip_hash;
    server backend1.example.com;
    server backend2.example.com down;
    server backend3.example.com;
}

least_conn#

least_conn 方法会选择每单位权重活动连接数最少的服务器——它最小化的是连接数与权重的比值,而不是连接的原始数量。因此,weight=2 且持有 15 个连接的服务器,会优先于 weight=1 且持有 10 个连接的服务器。若有多台服务器打平,则按加权轮询依次尝试。

只有当前正在处理请求的连接才计入其中;空闲的 keepalive 连接不计入。

当请求占用服务器的时间差异很大时,请使用它:例如大文件下载、长轮询、WebSocket。轮询会让每台服务器按单位权重收到相同数量的请求,但当一些请求只占用服务器几毫秒、另一些却占用数分钟时,这个数量几乎无法反映负载;least_conn 统计的则是服务器当前正在处理的请求数。如果服务器之间的差异在于速度,请改用 least_time。

random#

random 方法会按权重比例随机选择一台服务器。在大量请求下,实际分配会与权重相符。

two 参数会随机选出两台服务器,然后在两者中选择每单位权重活动连接数较少的那一台:

upstream backend {
    zone backend 1m;
    random two;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

当多个不共享状态的负载均衡器同时面向同一组服务器时,这一点尤为重要。如果每个均衡器都各自选择那台负载最轻的服务器,它们最终会一起挤向同一目标;而在两个随机候选者中选择较优者,则能把负载分散开来。若只有单个均衡器,least_conn 是更简单的选择。

备注

为了兼容性,random two least_conn 也可以被接受,其行为与 random two 完全相同;比较活动连接数是 two 唯一支持的模式。

least_time#

least_time 方法会优先选择响应更快的服务器。其模式决定测量的对象:header 表示接收响应头所需的时间,last_byte 表示接收完整响应所需的时间。

以下服务器组按接收响应头的时间来比较各服务器:

upstream backend {
    zone backend 1m;
    least_time header;
    server backend1.example.com;
    server backend2.example.com;
}

该测量值是一个指数加权移动平均值,因此单次缓慢的响应不会让服务器的评分产生太大波动。response_time_factor 指令(或 factor 参数)设置了历史累积值相对于每次新测量值所保留的权重:因子越高,反应越慢;因子越低,遗忘得越快。

在选用该方法之前,有两个特性值得了解。

它并不是单纯选择最快的服务器。服务器收到请求的几率与其平均响应时间成反比。即使是持续缓慢的服务器,也会保留一小部分份额——大约是普通服务器所获份额的百分之一,即对于有 N 台服务器的组,大约每 100 x N 个请求才轮到 1 个。这是刻意设计的:完全收不到请求的服务器永远无法产生新的测量值,也就永远无法被发现已经恢复。

它会拒绝 weight 参数。如果服务器组中的 least_time 与任何服务器上的 weight= 组合使用,该组将无法启动。响应时间本身已经反映了服务器正在承受的负载,权重也就无从体现了。

在 Stream 中,least_time 接受另一组模式: connect(与服务器建立连接所需的时间)、first_byte(从服务器接收到第一个数据字节所需的时间)和 last_byte(直到会话结束的时间);不支持 header。

feedback#

feedback (PRO) 方法让服务器自己表明希望接收多少工作量。Angie 会从每个响应中读取一个值——通常来自某个响应头——并按该值的比例为服务器分配请求份额,其求平均值的方式与 least_time 对响应时间求平均值的方式相同。

upstream backend {
    zone backend 1m;
    feedback $upstream_http_x_capacity;
    server backend1.example.com;
    server backend2.example.com;
}

警告

该值的读取方向符合"剩余容量"这一直觉:上报的值越高,获得的份额就越大。如果你的指标会随服务器越忙而升高——例如 CPU 负载、队列深度——请添加 inverse 参数,否则你会把最多的流量发往负载最重的服务器。

account 参数用于选择哪些响应参与平均值计算,这样就可以根据 upstream_probe (PRO) 的响应而不是客户端流量来驱动负载均衡。

服务器权重与流量份额#

weight 对不同方法的含义并不相同,只有在其中部分方法下,服务器的流量份额才等于其权重除以所有权重之和:

方法

weight 的作用

轮询、random

直接设定份额。份额等于权重除以总权重。

hash、ip_hash

设定键空间中的份额。只有当键本身均匀分布时,实际流量才会与权重相符;而真实的客户端地址和 URI 通常并非如此。

least_conn

对被比较的连接数进行折算。权重更高的服务器会被驱动到成比例更高的并发连接数,而不是成比例更高的请求数。

least_time

没有作用——该参数会在启动时被拒绝。

feedback

对服务器上报的值做乘法运算。权重只是两个因素之一,因此仅凭权重无法预测最终份额。

一旦涉及 max_conns、slow_start、max_fails、down 或备份服务器,就没有任何方法能严格按权重分配流量。

故障与恢复#

被动健康检查#

Angie 根据发送给服务器的请求结果判断其状态。如果某次尝试符合重试条件且尝试次数上限尚未用尽,Angie 可以把请求交给另一台服务器。proxy_next_upstream 及其同类指令定义这些条件;proxy_next_upstream_tries 和 proxy_next_upstream_timeout 限制尝试次数和总时长。

在 fail_timeout 时间段内出现 max_fails 次失败尝试,会导致服务器在同一时间段内被标记为不可用,此后会再次被尝试。默认值分别为 1 次尝试和 10 秒。何种情况算作失败尝试由 proxy_next_upstream 及其同类指令决定。

设置 max_fails=0 可禁用该服务器的检查:其永远不会被标记为不可用。

备注

在达到 max_fails 阈值之前,单次失败仍会减少服务器的请求份额,之后再逐步恢复。未配置 slow_start 时,Angie 会降低轮询和 least_conn 所使用的有效权重,再在后续请求中将其恢复。配置 slow_start 后,每次失败都会使服务器在该参数的时间爬升过程中后退;所有接受该参数的方法都是如此。这两种行为均要求 max_fails 大于 1:若使用默认值 1,第一次失败就会把服务器标记为不可用,因此不存在中间状态。

主动健康检查#

被动检查只能从客户端请求中了解服务器的状态:只有在发往该服务器的客户端请求已经失败之后才能发现故障,也只有在再次向其发送客户端请求时才能发现恢复。主动检查(仅 Angie PRO 提供)则不论客户端流量如何,都会按计划联系服务器,无需耗费客户端请求就能发现故障和恢复。

检查由 upstream_probe (PRO) 指令设置,该指令位于向服务器组传递请求的同一个 location 块中;服务器组本身需要共享内存区:

upstream backend {
    zone backend 1m;
    server backend1.example.com;
    server backend2.example.com;
}

map $upstream_status $good {
    200     "1";
}

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        upstream_probe backend_probe uri=/health interval=5s
                       test=$good fails=3 passes=2;
    }
}

Angie 每五秒向组内每台服务器请求一次 /health。连续三次检查失败会将服务器标记为不健康;连续两次检查通过则使其恢复服务。未指定 test 参数时,只有当此 location 中的客户端请求会被算作失败尝试时,检查才算失败。默认情况下,proxy_next_upstream 只把错误或超时算作失败,因此 500 或 503 响应不会使检查失败。此处 test=$good 只有在服务器响应 200 时才让检查通过:对于其他任何状态,map 都会使该变量为空。

检查响应还可以通过 account 参数为 feedback 方法提供数据。

被发现不健康的服务器会被所有负载均衡方法排除在选择范围之外。配置重载后,检查会重新开始。默认情况下,重载后服务器无需等待首次检查就继续接收请求。如果 upstream_probe 带有 essential 参数,则在检查首次发现服务器健康之前,服务器不会收到请求(在本例中,即连续两次检查通过之后);再加上仅能与 essential 一同使用的 persistent,则对重载前就健康的服务器取消这一等待,因为它们的健康标志保存在共享内存区中(参见 工作进程间共享的状态)。

备份服务器#

标记为 backup 的服务器会被保留作为备用:

upstream backend {
    zone backend 1m;
    server backend1.example.com;
    server backend2.example.com;

    server backup1.example.com backup;
}

每个请求都会先发往主服务器,只有当没有任何主服务器能够处理时,才会转到备份组。这不存在需要切回的持久故障转移状态——只要有一台主服务器重新通过健康检查,下一个请求就会使用它。

Angie PRO 可以通过 backup_switch (PRO) 的 permanent 参数反转这一行为,使请求持续发往最后一次实际处理过它们的那个组。不带时间参数时,切换不会自动恢复;若指定 permanent=time,Angie 会在该时间间隔后向主组发送一个请求,以检测其是否已恢复。

hash、ip_hash 和 random 都会拒绝 backup 参数。

让服务器恢复服务#

slow_start 参数会让恢复后的服务器在一段时间内逐步爬升到完整权重,而不是立即恢复满载,从而避免冷缓存或刚启动的进程一开始就承受满速流量:

server backend1.example.com slow_start=30s;

该参数最常在服务器被标记为不可用后恢复时生效;单次失败也会让服务器在同一爬升过程中后退(参见 故障与恢复)。它仅适用于 选择方法 中 slow_start 列标明的方法。与其他一些实现不同,它并不要求配置 zone 指令。

若要主动将某台服务器移出轮询,请使用 down;若要停止向其发送新流量,同时让已绑定的会话继续完成,请使用 drain。

工作进程间共享的状态#

只有当各个工作进程对服务器组的状态达成一致视图时,整个 Angie 实例的负载均衡才是连贯一致的。zone 指令正是为此而设计:

upstream backend {
    zone backend 1m;
    server backend1.example.com;
    server backend2.example.com;
}

如果不配置该指令,每个 工作进程 都会各自保留一份服务器组运行时状态的副本——失败计数器、活动连接数、轮询位置和响应时间平均值——并独立于其他工作进程进行负载均衡。有 N 个工作进程,实际上就相当于对同一组服务器运行了 N 个独立的均衡器:某个工作进程可能已将某台服务器标记为不可用,而其他工作进程仍在继续向它发送请求;max_conns 限制也是按工作进程分别生效的,实际连接数可能达到配置值的 N 倍。

共享内存区还使该服务器组对 上游指标 可见,并且 server 指令的 resolve 和 service 参数都要求配置它。

备注

无论 size 是否变化,配置重载都会重新创建共享内存区:计数器、被动检查状态和累积的负载均衡历史会从零开始。有两项内容会保留:通过 resolve 为服务器解析出的地址,以及带 persistent 参数的主动检查所依赖的健康标志(参见 主动健康检查)。

会话亲和性及相关控制#

负载均衡决定请求发往何处,而以下两个指令会对这一决策加以约束:

sticky

将客户端会话绑定到首次为其提供服务的服务器,绑定依据可以是 cookie、路由值,或者——使用 learn 时——Angie 从流量中观测到的某个值。会话绑定在整个会话期间会覆盖负载均衡方法。

queue (PRO)

在没有服务器能够处理请求时将其短暂挂起,而不是立即使其失败。适用于短时负载高峰导致 max_conns 达到上限的情况。

keepalive 指令并不影响选择哪台服务器:它让到服务器的空闲连接保持打开,这样发往已选定服务器的请求就可以复用其中一个连接,而不必新建连接。此类连接的数量按工作进程设上限,而不是按总数。

警告

sticky、keepalive 和 queue 指令必须出现在设置负载均衡方法的指令之后,否则不会生效。