bigywater.tech
微服务 · 18

负载均衡算法:nginx upstream 的实际分布,与手写轮询、加权轮询

nginx 的轮询、加权、ip_hash、hash、least_conn 实际怎么分布?后端速度不一样时 least_conn 好在哪?自己写一个平滑加权轮询,和 nginx 的选择顺序一样吗?这篇用三个后端逐项量出来。

500 / 100 / 100
weight=5:1:1 时 700 个请求的分布
115 对 533
一个慢后端(100 ms,其余 10 ms)上,least_conn 与轮询分给慢后端的请求数;吞吐 503/s 对 325/s
3 / 3
手写的平滑加权轮询与 nginx 真实选择顺序前 14 个比对一致的配置数(5:1:1、3:2:1、1:1:1)

分布

实测 · 本机执行输出
  指令                           | A   | B   | C  
  -----------------------------+-----+-----+----
  默认(轮询)                       | 234 | 233 | 233
  weight=5:1:1                 | 500 | 100 | 100
  weight=3:2:1                 | 350 | 233 | 117
  ip_hash(全是 127.0.0.1)        | 0   | 0   | 700
  hash $http_x_user            | 239 | 214 | 247
  hash $http_x_user consistent | 252 | 220 | 228
  random                       | 252 | 223 | 225
  random two least_conn        | 222 | 243 | 235

权重按比例分,哈希按键散开;ip_hash 在 client 地址全相同时没有负载均衡的作用。

least_conn

实测 · 本机执行输出
  指令                    | A(10 ms) | B(10 ms) | C(100 ms) | 延迟中位数 | p99    | 总吞吐  
  ----------------------+----------+----------+-----------+-------+--------+------
  轮询                    | 534      | 533      | 533       | 15 ms | 111 ms | 325/s
  least_conn            | 746      | 739      | 115       | 15 ms | 110 ms | 503/s
  random two least_conn | 724      | 749      | 127       | 15 ms | 110 ms | 471/s

后端速度不一样时,least_conn 让慢节点少收请求,总吞吐更高。

平滑加权轮询

实测 · 本机执行输出
  nginx 配置     | nginx 真实的前 14 个选择 | 手写 SWRR 的前 14 个 | 比对
  -------------+-------------------+-----------------+---
  weight=5:1:1 | AABACAAAABACAA    | AABACAAAABACAA  | 相同
  weight=3:2:1 | ABACBAABACBAAB    | ABACBAABACBAAB  | 相同
  weight=1:1:1 | ABCABCABCABCAB    | ABCABCABCABCAB  | 相同
  手写的普通轮询(RR)就是 i % n;加权轮询如果简单地把每个节点重复 weight 次再循环,会得到 AAAAABC 这样的序列(同一个节点连续被选很多次),平滑加权轮询把它们错开
  朴素的“重复 weight 次”序列(5:1:1)前 14 个: AAAAABCAAAAABC

把节点错开,不是连续选同一个;手写版本和 nginx 的顺序完全一致。

分步演示

负载均衡算法
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。nginx 是 Homebrew 的 1.31.6;后端是本进程里的 Python http.server;第一节顺序串行发 700 个请求;第二节 16 个线程各发 100 个;第三节的平滑加权轮询是我按 nginx 文档描述的算法写的 Python 版本
    • 没有验证:nginx 的 least_conn 在 worker 进程之间不共享计数(本机只有 1 个 worker 进程,多 worker 时的分布没有测);upstream 里 zone 共享内存的影响;nginx plus 的 least_time;权重和 backup / down 参数的组合
    京ICP备2026067502号-1京公网安备11010502063487号