跳转至

combined-upstreams: NGINX Combined Upstreams 模块

安装

您可以在任何基于 RHEL 的发行版中安装此模块,包括但不限于:

  • RedHat Enterprise Linux 7、8、9 和 10
  • CentOS 7、8、9
  • AlmaLinux 8、9
  • Rocky Linux 8、9
  • Amazon Linux 2 和 Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-combined-upstreams
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-combined-upstreams

通过在 /etc/nginx/nginx.conf 顶部添加以下内容来启用该模块:

load_module modules/ngx_http_combined_upstreams_module.so;

本文档描述了 nginx-module-combined-upstreams v2.3.1,于 2025 年 4 月 15 日发布。


The module introduces three directives add_upstream, combine_server_singlets, and extend_single_peers available inside upstream configuration blocks, and a new configuration block upstrand for building super-layers of upstreams. Additionally, directive dynamic_upstrand is introduced for choosing upstrands in run-time.

指令 add_upstream

用已定义的上游中列出的服务器填充主机上游,该上游由指令的强制第一个参数指定。服务器属性如权重、最大失败次数等保留在主机上游中。可选参数可以包括值 backup,将源上游的所有服务器标记为备份服务器,以及 weight=N,通过将其乘以因子 N 来校准源上游服务器的权重。

示例

upstream  combined {
    add_upstream    upstream1;            # src upstream 1
    add_upstream    upstream2 weight=2;   # src upstream 2
    server          some_another_server;  # if needed
    add_upstream    upstream3 backup;     # src upstream 3
}

指令 combine_server_singlets

从到目前为止在主机上游中定义的服务器生成多个 singlet upstreams。单个上游仅包含一个活动服务器,而其他服务器被标记为备份或不可用。如果没有传递参数,则单个上游将具有主机上游的名称,后面附加活动服务器在主机上游中的顺序编号。可以使用两个可选参数来调整它们的名称。第一个参数是添加到主机上游名称后和顺序编号前的后缀。第二个参数必须是一个整数值,定义顺序编号的 零对齐。例如,如果其值为 2,则顺序编号可以是 '01', '02', ..., '10', ... '100' ...

要将次要服务器标记为不可用而不是备份,请使用另一个可选参数 nobackup。该参数必须放在最后,在所有其他参数之后。

示例

upstream  uhost {
    server                   s1;
    server                   s2;
    server                   s3 backup;
    server                   s4;
    # build singlet upstreams uhost_single_01,
    # uhost_single_02, uhost_single_03 and uhost_single_04
    combine_server_singlets  _single_ 2;
    server                   s5;
}

为什么使用数字而不是名称?

在上面的示例中,单个上游将具有名称如 uhost_single_01,但包含服务器名称的名称如 uhost_single_s1 看起来更好且更方便。为什么不使用它们而是顺序编号?不幸的是,Nginx 在服务器添加到上游后不会记住服务器名称,因此我们无法简单地获取它们。

更新。 有个好消息!自版本 1.7.2 起,Nginx 在上游数据中记住服务器名称,现在我们可以在引用特殊关键字 byname 时使用它们。例如,

    combine_server_singlets  byname;
    # or
    combine_server_singlets  _single_ byname;

所有服务器名称中的冒号 (:) 都会被下划线 (_) 替换。

这在哪里可以有用

单个上游像一个具有回退模式的单一服务器。这可以用于管理粘性 HTTP 会话,当后端服务器通过适当的机制(如 HTTP cookies)识别自己时。

upstream  uhost {
    server  s1;
    server  s2;
    combine_server_singlets;
}

server {
    listen       8010;
    server_name  main;
    location / {
        proxy_pass http://uhost$cookie_rt;
    }
}
server {
    listen       8020;
    server_name  server1;
    location / {
        add_header Set-Cookie "rt=1";
        echo "Passed to $server_name";
    }
}
server {
    listen       8030;
    server_name  server2;
    location / {
        add_header Set-Cookie "rt=2";
        echo "Passed to $server_name";
    }
}

在此配置中,第一次客户端请求将随机选择后端服务器,所选服务器将将 cookie rt 设置为预定义值 (12),并且来自该客户端的所有后续请求将自动代理到所选服务器,直到它不可用。假设它是 server1,当它不可用时,客户端上的 cookie rt 仍将是 1。指令 proxy_pass 将把下一个客户端请求路由到单个上游 uhost1,其中 server1 被声明为活动,server2 被标记为备份。一旦 server1 不再可达,Nginx 将请求路由到 server2,后者将重写 cookie rt,并且所有后续客户端请求将被代理到 server2,直到它不可用。

指令 extend_single_peers

上游中的对等体根据指令 proxy_next_upstream 中列出的规则失败。如果一个上游在其主部分或备份部分中只有一个对等体,则该对等体将永远不会失败。当编写自定义算法以主动检查上游对等体的健康状况时,这可能是一个严重的问题。指令 extend_single_peers 在上游块中声明,如果该部分最初仅包含一个对等体,则在上游的主部分或备份部分添加一个标记为 down 的虚假对等体。这使得 Nginx 在原始单个对等体未通过 proxy_next_upstream 的规则时将其标记为失败,就像在多个对等体的一般情况下。

示例

upstream  upstream1 {
    server  s1;
    extend_single_peers;
}

upstream  upstream2 {
    server  s1;
    server  s2;
    server  s3 backup;
    extend_single_peers;
}

请注意,如果上游的某一部分(主部分或备份部分)包含多个对等体(如示例中的 upstream2 的主部分),则该指令没有效果:特别是在 upstream2 中,它仅影响上游的备份部分。

块 upstrand

旨在配置一个不失去其身份的上游超级层。接受多个指令,包括 upstreamordernext_upstream_statuses 等。以波浪号 (~) 开头的上游名称匹配正则表达式。只有在 upstrand 块定义之前已经声明的上游才被视为候选。

示例

upstrand us1 {
    upstream ~^u0 blacklist_interval=60s;
    upstream b01 backup;
    order start_random;
    next_upstream_statuses error timeout non_idempotent 204 5xx;
    next_upstream_timeout 60s;
    intercept_statuses 5xx /Internal/failover;
}

Upstrand us1 将组合所有名称以 u0 开头的上游和上游 b01 作为备份。备份上游在所有正常上游失败时进行检查。失败 意味着所有正常或备份周期中的上游都以指令 next_upstream_statuses 中列出的状态响应或被 黑名单。在这里,上游的响应 意味着上游最后一台服务器返回的状态,这受到指令 proxy_next_upstream 值的强烈影响。当上游具有参数 blacklist_interval 并以 next_upstream_statuses 中列出的状态响应时,该上游被设置为黑名单。黑名单状态在 Nginx 工作进程之间不共享。

接下来的四个 upstrand 指令类似于 Nginx 代理模块中的指令。

指令 next_upstream_statuses 接受 4xx5xx 状态符号及值 errortimeout,以区分在上游对等体连接中发生错误的情况与后端发送状态 502504 的情况(简单值 502504 以及 5xx 都指这两种情况)。它还接受值 non_idempotent,以允许在上游的最后一台服务器响应但根据指令中列出的其他状态失败时进一步处理 非幂等 请求。当请求的方法为 POSTLOCKPATCH 时,视为非幂等,就像在指令 proxy_next_upstream 中一样。

指令 next_upstream_timeout 限制 upstrand 在其所有上游之间循环的整体持续时间。如果在 upstrand 准备传递到下一个上游时时间已过,则返回最后一个上游周期的结果。

指令 intercept_statuses 通过拦截与给定 URI 匹配的位置中的最终响应来允许 upstrand failover。即使 upstrand 超时,拦截也必须发生。还要注意,在 upstrand 中遍历上游和 upstrand failover URI 是不可拦截的。更一般地说,任何内部重定向(通过 error_pageproxy_intercept_errorsX-Accel-Redirect 等)将破坏基于 upstrand 实现的嵌套子请求,从而导致返回空响应。这些都是极其糟糕的情况,这就是为什么遍历上游受到拦截保护的原因。upstrand failover URI 更容易受到此影响,因为实现对其位置的控制较少。特别是,upstrand failover 仅对 error_pageproxy_intercept_errors 的拦截提供保护。这意味着 upstrand failover URI 位置必须尽可能简单(例如,使用简单的指令,如 returnecho)。

也就是说,解决 upstrand failover 位置及其内部重定向问题有一个不错的解决方案。内部重定向究竟是如何 破坏 子请求的?好吧,它们 擦除 模块响应过滤器中所需的子请求上下文。因此,如果我们能够使子请求上下文持久化,是否就能解决这个问题?答案是肯定的!Nginx 模块 nginx-easy-context 使构建持久请求上下文成为可能。upstrands 可以通过在 config 文件中打开开关并构建两个模块来受益于它们。有关详细信息,请参见 构建和测试 部分。

指令 order 目前仅接受一个值 start_random,这意味着在工作进程启动后,正常和备份周期中的上游将随机选择。后续请求中的上游将以轮询方式循环。此外,指令 order 中还接受修饰符 per_request:它关闭全局每个工作进程的轮询循环。per_requeststart_random 的组合使每个新请求中的起始上游随机选择。

通过向 proxy_pass 指令提供以 upstrand_ 开头的特殊变量,可以在单个请求期间实现这种 失败 状态之间的故障转移,如下所示:

location /us1 {
    proxy_pass http://$upstrand_us1;
}

在访问其他指令中的此变量时要小心!它启动了子请求机制,这在许多情况下可能并不理想。

Upstrand 状态变量

有多个可用的 upstrand 状态变量:upstrand_addrupstrand_cache_statusupstrand_connect_timeupstrand_header_timeupstrand_response_lengthupstrand_response_timeupstrand_status。它们都是相应 upstream 变量的对应物,并包含请求和所有子请求按时间顺序传递的所有上游的后者的值。变量 upstrand_path 包含请求期间访问的所有上游的路径。

这在哪里可以有用

upstrand 看起来非常类似于一个简单的组合上游,但它还有一个关键区别:upstrand 内部的上游不会被扁平化,并且保持其身份。这使得可以为与单个上游关联的服务器组配置 failover 状态,而无需逐一检查它们。在上面的示例中,upstrand us1 可以包含上游列表,如 u01u02 等。想象一下,上游 u01 内部包含 10 台服务器,并代表一个地理分布式后端系统的一部分。让 upstrand us1 将所有这些部分组合在一起,并让我们运行一个客户端应用程序,轮询这些部分以执行某些任务。让后端在没有新任务时发送 HTTP 状态 204。在一个扁平的组合上游中,所有 10 台服务器可能在应用程序最终从另一个上游接收新任务之前都被轮询。upstrand us1 允许在检查上游中没有任务的第一个服务器后跳过下一个上游。这种机制显然适合 upstream broadcasting,当消息被发送到 upstrand 中的所有上游时。

上述示例表明,upstrand 可以被视为一个 二维 上游,包含多个代表自然上游的集群,并允许在它们之间进行短周期。

为了说明这一点,让我们模拟一个没有轮询平衡的上游。每个新的客户端请求将首先代理到上游列表中的第一个服务器,然后故障转移到下一个服务器。

    upstream u1 {
        server localhost:8020;
        server localhost:8030;
        combine_server_singlets _single_ nobackup;
    }

    upstrand us1 {
        upstream ~^u1_single_ blacklist_interval=60s;
        order per_request;
        next_upstream_statuses error timeout non_idempotent 5xx;
        intercept_statuses 5xx /Internal/failover;
    }

指令 combine_server_singlets 在上游 u1 中生成两个单个上游 u1_single_1u1_single_2,以容纳 upstrand us1。由于 upstrand 内部的 per_request 排序,这两个上游将在每个客户端请求中按顺序 u1_single_1 → u1_single_2 遍历。

指令 dynamic_upstrand

允许在运行时从传递的变量中选择一个 upstrand。该指令可以在服务器、位置和 location-if 子句中设置。

在以下配置中

    upstrand us1 {
        upstream ~^u0;
        upstream b01 backup;
        order start_random;
        next_upstream_statuses 5xx;
    }
    upstrand us2 {
        upstream ~^u0;
        upstream b02 backup;
        order start_random;
        next_upstream_statuses 5xx;
    }

    server {
        listen       8010;
        server_name  main;

        dynamic_upstrand $dus1 $arg_a us2;

        location / {
            dynamic_upstrand $dus2 $arg_b;
            if ($arg_b) {
                proxy_pass http://$dus2;
                break;
            }
            proxy_pass http://$dus1;
        }
    }

在变量 dus1dus2 中返回的 upstrands 将根据变量 arg_aarg_b 的值进行选择。如果设置了 arg_b,则客户端请求将发送到名称等于 arg_b 值的 upstrand。如果没有具有此名称的 upstrand,则 dus2 将为空,proxy_pass 将返回 HTTP 状态 500。为了防止用空值初始化动态 upstrand 变量,其声明必须以对应于现有 upstrand 的文字名称结束。在此示例中,如果 arg_a 为空或未设置,动态 upstrand 变量 dus1 将通过 upstrand us2 初始化。总之,如果 arg_b 未设置或为空且 arg_a 已设置并且值等于现有 upstrand,则请求将发送到该 upstrand;否则(如果 arg_b 未设置或为空且 arg_a 已设置但不指向现有 upstrand),proxy_pass 很可能返回 HTTP 状态 500(除非存在由文字字符串 upstrand_arg_a 的值组成的变量,指向有效目标),否则(arg_barg_a 均未设置或为空)请求将发送到 upstrand us2

另请参阅

我的博客中有几篇关于该模块的文章,按时间顺序排列:

  1. Простой модуль nginx для создания комбинированных апстримов(俄语)。一篇全面的文章,揭示了指令 add_upstream 的实现细节,也可以视为 Nginx 模块开发的小教程。
  2. nginx upstrand to configure super-layers of upstreams。关于块 upstrand 使用的概述及其实现的一些细节。
  3. Не такой уж простой модуль nginx для создания комбинированных апстримов(俄语)。对该模块所有功能的概述,包含配置示例和测试会话样本。

GitHub

您可以在 nginx-module-combined-upstreams 的 GitHub 仓库中找到该模块的其他配置提示和文档。