Nginx 负载均衡

Nginx 以高并发、低消耗而闻名,这个特点使其很适合作为一个负载均衡器 (Load Balancer),有策略地分发请求给不同的后端服务器。避免单点故障之余,亦增强整个系统的可用性,简单说不容易宕机。负载均衡是反代的其中一个用途。本文介绍 Nginx 常用的几个负载均衡策略。

⚠️ 假设有一组名为 backend 的 upstream,那么调用方式是 proxy_pass http://backend。

热备 (Hot Standby)

在此例中,平时使用 www.example.com 提供服务,bad.example.com 则暂时下线,bak.example.com 作为发生故障时兜底的一台机器。又称故障转移 (failover)。

upstream backend {
    server www.example.com;
    server bad.example.com down;
    server bak.example.com backup;
}

轮询 (round-robin)

介个是经(mò)典(rèn)的负载均衡算法。理论上会跳过不能用的 server,但预设的超时太长,实务上可改成 proxy_connect_timeout 1、proxy_read_timeout 1 和 proxy_send_timeout 1。

按照时间先后来分发请求。在此例中,顾名思义第 1 个 request 走 srv1.example.com,第 2 个走 srv2.example.com,第 3 个走 srv3.example.com,第 4 个走 srv1.example.com……以此类推。

upstream backend {
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}

最小连接 (least-connected)

因应忙闲不均、负载不均的问题而生。在此例中,Nginx 将试图减轻已经很忙的服务器的压力,把新请求分发至没那么忙的主机。

若对动态负载均衡的算法感兴趣,还有一插件 nginx-upstream-fair 可以实现最快响应法。

upstream backend {
    least_conn;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}

源地址哈希 (ip-hash)

Session 用于跟踪用户操作,涉及身份认证时 (用户系统) 往往能派上用场,而通常情况下会话信息保存在单机上,这就使得有些需求得在同一台服务器上完成,不能换到其他的服务器 (姑且不谈分布式 Session)。

Sticky Sessions (粘滞会话) 将保证一个用户对应一台服务器,从而解决 Session 不一致的问题 (通常表现为无法登录)。出于容灾理由,不建议大量使用。

upstream backend {
    ip_hash;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}

加权负载均衡 (Weighted Load Balancing)

集群中难免有算力不均的情况。不同服务器处理速度有快有慢,不问性能的齐头式平等,很可能触发木桶原理的副作用——让配置最差的那个服务器决定整个服务器集群的性能。可跑一次 bench.sh,再依据测试结果给服务器排个序。

在此例中,每 10 个新请求有 6 个被分配至 srv1.example.com,然后各有 2 个被分配至 srv2.example.com 和 srv3.example.com。此例演示的轮询法,事实上最小连接和源地址哈希法也可以分配权重。

upstream backend {
    server srv1.example.com weight=3;
    server srv2.example.com;
    server srv3.example.com;
}

运行状况检查 (Health Checks)

根据 Nginx 的文档,max_fails 缺省值是 1,fail_timeout 缺省值是 10s。

在此例中,www.example.com 的健康检查会被关闭,一直都标记为可用;www2.example.com 连不上时先重试 2 次,如果还不行就退出服务,下线 1 天,方便运维人员排障。

upstream backend {
    server www.example.com max_fails=0;
    server www2.example.com max_fails=2 fail_timeout=1d;
}

关于指令 proxy_next_upstream

根据 Nginx 的文档,proxy_next_upstream 含括下列数种情况:

error 建立连接 / 发送请求 / 接收响应时出错(缺省值之一);
timeout 建立连接 / 发送请求 / 接收响应时超时(缺省值之一);
invalid_header 上游返回空白或无效响应;
http_500 上游返回 500 Internal Server Error;
http_501 上游返回 501 Not Implemented;
http_502 上游返回 502 Bad Gateway;
http_503 上游返回 503 Service Unavailable;
http_504 上游返回 504 Gateway Timeout;
http_404 上游返回 404 Not Found;
http_429 上游返回 429 Too Many Requests;
non_idempotent 解除对非幂等请求 (POST, LOCK, PATCH) 的封印,小心造成重复提交;
off 不得转给下一台服务器。

一般来说,即使某一台后端服务器返回了 500,这台服务器也会参与负载均衡,毕竟能收到 HTTP 状态码,就表示它还活着。但这样的结果在用户眼里跟 Connection Refused 以及 Operation Timed Out 可没啥区别,所以在此例中,把 500 一并纳入“在下一台服务器重试”的机制里。

location / {
    ...
    proxy_pass http://backend;
    proxy_next_upstream error timeout http_500;
    ...
}