nginx负载均衡的核心是upstream模块而非worker进程,worker仅执行请求转发,分发决策由upstream在proxy_pass阶段实时完成,基于轮询、least_conn、ip_hash等算法结合后端状态独立计算,各worker通过共享内存同步健康信息并无锁协作。

Nginx 的 Worker Process 本身不实现请求分发算法,它只负责接收连接、读取请求、转发给 upstream,并把响应返回客户端。真正的“请求分发算法”(如轮询、least_conn、ip_hash)是在 upstream 模块内部由 master 进程初始化、worker 进程在 proxy_pass 时调用的逻辑决定的——不是 worker 进程自己“算”怎么分,而是按已配置的 upstream 策略查表或计算后选择后端服务器。
换句话说:
Worker 进程是执行者,upstream 是策略引擎;分发决策发生在 proxy_pass 阶段,而非 accept 或 event loop 阶段。
请求分发的实际发生时机
当一个 HTTP 请求完成解析(比如 location /api 匹配成功),且配置了 proxy_pass http://backend; 时,Nginx 才会触发 upstream 模块:
- 根据
upstream backend { ... }中定义的算法和 server 列表; - 结合当前各 server 的状态(是否
down、max_fails是否超限、active connections 数等); - 实时计算出本次请求该交给哪台后端服务器;
- 然后建立连接、转发请求。
这个过程对每个请求独立进行,不依赖全局调度器,也不跨 worker 共享状态(除共享内存中的健康状态外)。
Worker 进程如何协同?靠的是无锁竞争 + 共享状态
虽然分发逻辑不在 worker 内部“做决策”,但多个 worker 能高效协作,依赖以下机制:
- accept_mutex 竞争:防止惊群效应,确保只有一个 worker 接收新连接;
- epoll/kqueue 事件驱动:每个 worker 独立监听 socket 读写事件,处理自己 accept 到的连接;
-
upstream 共享状态:
max_fails、fail_timeout、down状态通过共享内存(shared memory zone)同步,所有 worker 查看到一致的后端健康视图; - least_conn / ip_hash 等计算是线程安全的:Nginx 使用原子操作或只读查表(如 ip_hash 的哈希桶),避免锁开销。
✅ 举例:
ip_hash对$remote_addr做 CRC32 取模,结果直接映射到 server 数组下标;least_conn遍历所有可用 server,找active_connections最小的那个——这些都在单次请求上下文中完成,不阻塞其他 worker。
不同算法在 worker 中的表现差异
| 算法 | worker 中如何体现 | 关键依赖 |
|---|---|---|
| 轮询 / 加权轮询 | 维护一个全局(per-upstream)的 current_weight 或 index 计数器,每次请求递增并取模 | 共享内存中的计数器(原子更新) |
| ip_hash | 对客户端 IP 哈希后取模,直接定位 server |
$remote_addr 或 $binary_remote_addr(更精确) |
| least_conn | 遍历所有 upstream.server,比较 active_connections 字段 |
实时读取每个 server 的连接数(内存变量) |
| url_hash / fair(第三方) | 类似 ip_hash,但哈希源不同;fair 需读取响应时间统计 | 需额外模块支持,部分依赖 Lua 或 OpenResty |
注意:所有这些计算都发生在 ngx_http_upstream_init_request 或后续 get_peer 回调中,属于 upstream 框架的一部分,worker 只是调用它。
为什么不能靠 worker 自己“均衡”?
因为:
- worker 之间不通信,无法知道彼此当前处理多少请求;
- 每个 worker 的连接数、请求速率、延迟都不同,局部视角无法代表全局负载;
- Nginx 设计哲学是「简单可靠」:把分发逻辑收敛到 upstream,让 worker 专注 I/O 和协议处理。
所以,所谓“Nginx 负载均衡”,本质是 每个请求在转发瞬间,由统一 upstream 策略做出最优(或约定)选择,而不是靠 worker 进程间协调分配。
不复杂但容易忽略。











