ChrisKim
Do not go gentle into that good night.
颢天

基于 Nginx 实现大模型接口负载均衡与故障转移

近期我在多台服务器上通过 vLLM 部署了大语言模型提供 API 服务,以此进行大批量模型推理数据生成任务。在多节点的大模型推理中,负载均衡和故障转移对于效率和稳定性是至关重要的。在经过研究后,我觉得使用 Nginx 来完成这个任务是最简单和成熟的,本篇文章将会介绍实现方式。

1 原理

在最开始,我也认为我不需要做什么专门的负载均衡和故障转移,我只需要在我的数据生成任务的代码中实现简单的负载均衡和重试逻辑就行了。但是代码跑起来后我就意识到,原来这个问题并没有这么简单。

例如,我最开始的负载均衡就是单纯在 Python 代码中实现了模型地址的轮转逻辑,以此来保证交替请求每一个节点的模型:

class RoundRobinBaseURLPool:
    def __init__(self, base_urls: list[str]):
        self.base_urls = base_urls
        self.index = 0
        self.count = len(base_urls)

    def get_base_url(self) -> str:
        base_url = self.base_urls[self.index]
        self.index = (self.index + 1) % self.count
        return base_url

这看起来非常正确,在最初运行时也看起来非常正确,我使用 384 线程的并发,两个节点正确地均匀收到了 192 个并发请求。但是在我代码挂了半天之后我发现,有一个节点在某个时刻请求量突然增大到 350 左右,而另一个节点持续保持在 30 左右的请求,并且后期一直保持这样的比例,这让我百思不得其解。

在经过一些实验之后,我突然理解了为什么会产生这个问题,我之前的轮转逻辑实际是脆弱的平衡,稍有扰动就会永远倾斜:

  • 一个模型节点,收到并发请求量越大,每个请求的生成速度会变慢,请求的完成耗时会变大。
  • 轮转法负载均衡并不考虑任务完成时间,当一个节点的请求完成耗时比另一个节点大时,它在单位时间内会获得更多请求。
  • 慢的节点在单位时间会获得更多请求,由于请求量增大,该节点会变得更慢,进入恶性循环。

如果要解决这个问题,最合适的方法就是考虑每个节点当前正在处理的请求数目,保证每个节点正在处理的请求数目相同。要实现这个,通过调用时的逻辑就很难完成了,必须引入额外的工具来协助负载均衡。

2 方法

Nginx 是一个成熟且高效的 Web 服务器,它已经实现了负载均衡的相关功能,我们的大模型 API 服务也是一个典型的 Web 服务,因此直接利用 Nginx 是最合适的方案。

2.1 安装 Nginx

安装 Nginx 有各种方式,我这里选择通过 Docker 启动,直接给出启动指令,过程不再赘述。

mkdir /opt/nginx
touch /opt/nginx/nginx.conf
mkdir /opt/nginx/conf.d
mkdir /opt/nginx/cert
mkdir /opt/nginx/log
mkdir /opt/nginx/html

docker run -d \
  --name=nginx \
  --net=host \
  -v /opt/nginx/nginx.conf:/etc/nginx/nginx.conf \
  -v /opt/nginx/conf.d:/etc/nginx/conf.d \
  -v /opt/nginx/cert:/etc/nginx/cert \
  -v /opt/nginx/log:/var/log/nginx \
  -v /opt/nginx/html:/usr/share/nginx/html \
  --restart=always \
  nginx:latest

需要向 /opt/nginx/nginx.conf 写入默认 Nginx 配置:

#user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

2.2 配置 Nginx

负载均衡和故障转移(upstream 块)

首先,最重要的就是配置负载均衡的逻辑,Nginx 有以下负载均衡方法:

  • 轮询(默认):每个节点依次获得请求。
  • 最少连接(least_conn):新请求到达时分配给目前连接数最少的节点。
  • IP 哈希(ip_hash):根据客户端的 IP 地址哈希选择节点。

如果理解了第 1 章分析的问题,那么想必读者已经知道该场景需要选择的是最少连接(least_conn)方法。

然后,多节点的故障转移也是需要考虑的,当一个节点故障后应当及时剔除。Nginx 的故障转移逻辑通过两个参数指定:

  • 最大失败次数(max_fails):当一个节点请求失败超过该次数,将被认定为故障。
  • 故障时间(fail_timeout):一个节点被认定为故障后,Nginx 停止向其发送请求的时间,度过该时间后 Nginx 会恢复该节点的请求。

我的每个节点模型均配置了崩溃自动重启,每次重启大概需要两分钟时间,因此我设定最大失败 5 次,故障时间 120 秒。

综上,负载均衡和故障转移的配置如下。第 1 行 vllm 是自己命名的上游节点组名称,第 2 行是指定的负载均衡方法,下面的 server 行便是每个节点的地址和故障转移配置了。

upstream vllm {
    least_conn;
    server 192.168.0.150:9997 max_fails=5 fail_timeout=120s;
    server 192.168.0.58:9997  max_fails=5 fail_timeout=120s;
}

反向代理配置(location 块)

由于 Nginx 做负载均衡,实际上是对原始服务器做了反向代理,因此需要编写反向代理相关的配置。这相关的配置比较简单,我在注释中简单解释下。

location / {
    proxy_pass http://vllm;      # 上游节点地址,http后直接填写上游节点组名称
    proxy_read_timeout 86400s;   # 最长连接时间,由于大模型连接时长往往很长,因此设为1天
    proxy_buffering off;         # 关闭缓存,防止干扰大模型的流式响应输出
    proxy_cache off;             # 关闭缓存,防止干扰大模型的正常响应
    proxy_next_upstream error timeout http_500 http_502 http_503 http_504; # 认定为故障的事件
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

完整配置文件

最后整合好的完整配置文件如下,将其写入 /opt/nginx/conf.d/vllm.conf 后重载 Nginx 即可:

upstream vllm {
    least_conn;
    server 192.168.0.150:9997 max_fails=5 fail_timeout=120s;
    server 192.168.0.58:9997  max_fails=5 fail_timeout=120s;
}

server {
    listen       9999;
    server_name  _;

    access_log  /var/log/nginx/vllm.access.log  main;
    error_log   /var/log/nginx/vllm.error.log   warn;

    location / {
        proxy_pass http://vllm;
        proxy_read_timeout 86400s;
        proxy_buffering off;
        proxy_cache off;
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

其中,listen 参数设定了 Nginx 接受客户端请求的端口号,这里的意思是在 9999 端口接受客户端请求,再根据负载均衡的逻辑,转发给 192.168.0.150192.168.0.589997 端口。

3 效果

3.1 负载均衡

可以看到,正常情况下两个节点的请求数是平衡的。

https://assets.zouht.com/img/blog/4130-03.webp

3.2 故障转移

当 2 号节点崩溃后,可以看到 1 号节点的请求量激增,Nginx 将故障的 2 号节点的请求导向了 1 号节点。

P.s. vLLM-Ascend 真的很不稳定啊,几个小时崩一次。

https://assets.zouht.com/img/blog/4130-01.webp

当 2 号节点崩溃自动重启后,1 号节点的请求量开始下降,2 号节点的请求量开始上升,Nginx 开始恢复节点间的负载均衡。

P.s. 这个过程不是瞬间的,需要一段时间逐渐平衡,猜测是 Nginx 做了什么其他逻辑,防止请求突增。

https://assets.zouht.com/img/blog/4130-02.webp
本文链接:https://www.zouht.com/4130.html
本文使用:CC BY-NC-SA 4.0 许可
# # # # # #
首页      技术      基于 Nginx 实现大模型接口负载均衡与故障转移

发表回复

textsms
account_circle
email

颢天

基于 Nginx 实现大模型接口负载均衡与故障转移
近期我在多台服务器上通过 vLLM 部署了大语言模型提供 API 服务,以此进行大批量模型推理数据生成任务。在多节点的大模型推理中,负载均衡和故障转移对于效率和稳定性是至关重要的。在经…
扫描二维码继续阅读
2025-10-28