大模型应用实战:从0到1教你构建大模型服务高可用架构设计(支持 100万用户并发)

Vllm-v0.11.0

Vllm-v0.11.0

Vllm

vLLM是伯克利大学LMSYS组织开源的大语言模型高速推理框架,旨在极大地提升实时场景下的语言模型服务的吞吐与内存使用效率。vLLM是一个快速且易于使用的库,用于 LLM 推理和服务,可以和HuggingFace 无缝集成。vLLM利用了全新的注意力算法「PagedAttention」,有效地管理注意力键和值

《大模型应用实战:从0到1教你构建大模型服务高可用架构设计(支持 100万用户并发)》

第一部分:开篇与基础 (Chapter 1: The “Why” and The “What”)
1.1 引言:为什么你的单个大模型应用正在“裸奔”?

你是否也曾经历过这样的时刻:在本地精心调试好一个大模型应用,它聪明、响应迅速,你满怀激动地将它部署到服务器上,分享给朋友或者首批用户。一开始,一切顺利。但随着使用者越来越多,应用的响应开始变得像在泥潭中跋涉,甚至时不时地直接给你一个冰冷的“服务不可用”错误。

如果这听起来很熟悉,那么恭喜你,你遇到的不是一个简单的 bug,而是一个架构问题。将一个能跑起来的大模型应用,直接暴露在公网上,就如同让它在复杂的网络世界中“裸奔”。它毫无保护,也毫无扩展能力,任何一点风吹草动都可能让它瞬间崩溃。

这背后的根本原因,在于单个服务实例,天然就面临着“三座大山”:

  1. 性能瓶颈:一个GPU、一个进程的处理能力终究是有限的。当每秒钟涌入成百上千个请求时,它们只能排队等待,延迟自然急剧上升。
  2. 单点故障 (Single Point of Failure):你的所有希望都寄托在这一个实例上。一旦这个进程因为代码错误、内存溢出或服务器宕机而崩溃,你的整个服务就彻底瘫痪了。
  3. 无法扩展 (Scalability):面对突发流量,比如你的应用突然火了,用户量暴增十倍,你怎么办?单个实例无法像孙悟空一样拔毛变出分身来处理暴增的请求。

本教程的目的,就是带领你,一步一步,为你的大模型应用穿上坚固的“盔甲”,构建一个工业级的服务架构。它不仅能轻松应对百万用户的并发请求,还能在部分组件出现故障时“面不改色”,实现真正的高可用。忘掉那些晦涩的理论,我们将通过最直接的实战,让你亲手搭建起这一切。准备好了吗?让我们开始吧。

1.2 蓝图概览:我们的“百万并发”架构长什么样?

在开始“施工”之前,我们先来看看最终的“建筑蓝图”。一个清晰的目标能让我们在后续的实践中不至迷失方向。我们的架构,简单来说,就是一套分工明确、配合默契的团队体系。

下面就是我们将要构建的系统架构图:

基础设施 (云服务器)
负载均衡层
推理服务层 (可水平扩展)
缓存层 (高可用)
监控与告警层
用户端
轮询分发
轮询分发
轮询分发
抓取指标
抓取指标
抓取指标
抓取指标
抓取指标
查询数据
Prometheus
Grafana Dashboard
Redis 集群
vLLM 实例 1 on GPU
vLLM 实例 2 on GPU
vLLM 实例 N on GPU
Nginx Load Balancer
用户请求

让我们花一分钟来理解这个图里的每个角色:

  • 用户请求 (User):代表了千千万万的终端用户。
  • 负载均衡层 (Nginx):他是我们系统的“前台接待”和“交通指挥官”。所有用户的请求都先到达这里,由它来决定将请求转发给后面哪一个具体的推理服务实例,确保雨露均沾,没有谁被累死,也没有谁在偷懒。
  • 推理服务层 (vLLM):这是我们系统的“大脑”,真正执行大模型计算的地方。我们部署了多个实例,形成一个“大脑集群”,它们干的活完全一样,人多力量大。这个集群可以根据流量大小,动态地增加或减少实例数量(即水平扩展)。
  • 缓存层 (Redis):这是我们系统的“高速缓存”。对于那些重复的、常见的用户问题,我们没必要每次都让“大脑”重新思考一遍。第一次计算出结果后,我们就把它存到 Redis 里。下次再有相同的请求,直接从 Redis 取用,速度极快,还能给“大脑”减负。
  • 监控层 (Prometheus + Grafana):这是我们系统的“体检中心”和“仪表盘”。Prometheus 负责不知疲倦地收集各个组件的健康数据(比如请求量、延迟、资源占用率),而 Grafana 则将这些冰冷的数据,以酷炫的图表形式展现在我们面前,让我们对整个系统的运行状况了如指掌。

通过这样一套组合拳,我们就能实现高并发处理、故障自动转移、系统状态可视化,从而打造出一个真正健壮的服务。

1.3 环境准备:搭建我们的“施工现场”

巧妇难为无米之炊。在正式编写代码和配置之前,我们需要准备好我们的“工具箱”和“场地”。

  • 软件清单与安装指南

    1. Docker 与 Docker Compose: 这是我们整个项目的基石。

      • 为什么用它? 想象一下,你要部署 Nginx、vLLM、Redis… 每个软件都有自己复杂的依赖和配置。如果在你的电脑上直接安装,很容易出现版本冲突,换一台机器可能就跑不起来了。Docker 把每个服务(如 Nginx)和它的所有依赖打包到一个独立的、标准化的“集装箱”(Container)里,保证它在任何安装了 Docker 的机器上都能以完全相同的方式运行。Docker Compose 则是用来“编排”这些集装箱的工具,让我们能用一个文件就定义和启动我们整个多服务应用。
      • 如何安装? 请访问 Docker 官方网站,根据你的操作系统(Windows, macOS, Linux)下载并安装 Docker Desktop。它已经内置了 Docker Compose,无需额外安装。
    2. 云服务器: 本地计算机的 GPU 资源通常有限,为了模拟真实生产环境,我们需要一台带 GPU 的云服务器。

      • 为什么需要? 大模型推理是计算密集型任务,严重依赖 GPU 进行加速。没有 GPU,推理速度会慢到无法接受。
      • 如何选择? 亚马逊的 AWS (EC2 P-series, G-series)、Google Cloud (GCE with NVIDIA GPUs)、阿里云 (ECS GN-series) 都是不错的选择。对于本教程,建议选择一台配备至少一张 NVIDIA T4 或 A10G GPU,内存 32GB 以上的实例。
    3. 压力测试工具 (JMeter): 我们的“万箭齐发”模拟器。

      • 为什么用它? 我们如何知道我们的架构能否扛住十万甚至百万的并发?总不能真的去找这么多人来同时访问吧。JMeter 就是一个能模拟大量用户同时发送请求的工具,帮助我们测试系统的性能和极限。
      • 如何安装? JMeter 是一个 Java 程序,需要先安装 Java 环境(JDK 8+)。然后从 Apache JMeter 官网 下载二进制包,解压即可使用。
    4. Git: 代码版本控制工具。

      • 为什么用它? 良好的习惯。它可以帮助我们追踪每一次对代码和配置的修改,方便回滚和协作。
      • 如何安装? 访问 Git 官网 下载并安装。
  • 项目初始化

    好了,工具都备齐了。现在,打开你的终端(命令行工具),让我们来创建项目的根目录,并迈出坚实的第一步——创建我们整个架构的“总指挥文件”docker-compose.yml

    # 创建一个名为 llm-arch-project 的文件夹,并进入该文件夹
    mkdir llm-arch-project
    cd llm-arch-project
    
    # 创建一个空的 docker-compose.yml 文件
    # 这个文件将是我们的核心,它会定义我们架构中的所有服务
    touch docker-compose.yml
    
第二部分:核心架构搭建 (Chapter 2: Building the Backbone)
2.1 推理服务层:让大模型“分身有术” (vLLM)

推理服务层是整个系统的心脏和大脑,我们选择 vLLM 作为我们的推理引擎。

  • 为什么是 vLLM?
    vLLM 是一个专为大语言模型(LLM)推理设计的高性能引擎。它通过一项名为 PagedAttention 的关键技术,解决了传统推理服务中显存管理效率低下的问题,使得吞吐量(单位时间内能处理的请求数)相比传统实现有数倍的提升。选择它,意味着我们从一开始就站在了高性能的起点上。

  • 第一步:部署单个 vLLM 实例
    我们先从一个实例开始。打开上一章创建的 docker-compose.yml 文件,输入以下内容。这是我们定义的第一个服务。

    # docker-compose.yml
    version: '3.8' # 指定 docker-compose 文件格式的版本
    
    services: # 定义我们应用中的所有服务
      # 定义我们的第一个 LLM 推理服务
      vllm:
        # 使用 vllm-openai 官方提供的镜像,该镜像内置了 NVIDIA CUDA 环境
        image: vllm/vllm-openai:latest
        
        # 部署相关的配置
        deploy:
          resources:
            reservations:
              devices:
                # 请求 Docker 分配所有可用的 GPU 给这个容器
                # 这是运行大模型推理的关键
                - driver: nvidia
                  count: all
                  capabilities: [gpu]
        
        # 容器启动时要执行的命令
        command:
          - "--model"
          # 这里我们使用一个较小的模型作为示例,你可以换成自己的模型
          # 例如:'facebook/opt-125m' 或指向你本地模型路径
          - "meta-llama/Llama-2-7b-chat-hf" 
          # 注意:使用 Llama2 等模型需要 Hugging Face 登录凭据
          # 你需要提前配置好 Docker 对 Hugging Face 的访问权限
          # 或者将模型文件提前下载到服务器上,并通过 volumes 挂载进去
    
        # 容器的重启策略
        # 'always' 表示无论因何种原因退出,Docker 都会自动尝试重启它
        restart: always
        
        # 端口映射: 将容器内部的 8000 端口暴露给宿主机的 8000 端口
        # 这样我们就可以通过服务器的 8000 端口访问到 vLLM 服务
        ports:
          - "8000:8000"
    

    代码解释

    • image: 我们直接使用了官方提供的、预装好所有环境的 Docker 镜像,省去了自己配置 CUDA 和 Python 依赖的麻烦。
    • deploy.resources.reservations.devices: 这是在告诉 Docker:“请把这台服务器上的 GPU 资源分配给这个容器使用”,这是性能的保证。
    • command: 这里是 vLLM 的启动命令。--model 参数指定了我们要加载的模型。为了方便演示,我们使用了 Hugging Face 上的一个模型。在生产环境中,你通常会把模型文件下载到服务器,通过 volumes 挂载到容器里,以加快启动速度。
    • restart: always: 这是一个简单的容灾机制。如果 vLLM 服务因为某些原因意外崩溃,Docker 会像一个尽职的管理员一样,立即将它重新拉起。

    写好之后,在终端的 llm-arch-project 目录下,运行以下命令启动服务:

    # -d 表示在后台运行 (detached mode)
    docker-compose up -d
    

    等待 Docker 拉取镜像并启动容器。你可以通过 docker ps 查看容器状态,或通过 docker logs <容器名> 查看 vLLM 的启动日志。启动成功后,打开另一个终端,用 curl 命令来测试它:

    curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "meta-llama/Llama-2-7b-chat-hf",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }'
    

    如果一切顺利,你将收到模型生成的补全文本。恭喜,你的第一个推理服务实例已经成功运行!

  • 第二步:轻松扩展,部署多个实例
    现在,如何从1个实例扩展到3个?在 Docker Compose 的世界里,这简直易如反掌。我们不需要再启动两台服务器,只需要修改 docker-compose.yml 文件。

    但我们不能直接复制粘贴,因为端口会冲突。而且,我们即将引入 Nginx 做统一入口,所以这些 vLLM 实例不再需要直接暴露端口给外部。修改后的文件如下:

    # docker-compose.yml (版本2)
    version: '3.8'
    
    services:
      # 我们将 vLLM 服务重命名为 vllm-1,以示区分
      vllm-1:
        image: vllm/vllm-openai:latest
        deploy:
          resources:
            reservations:
              devices:
                - driver: nvidia
                  count: 1 # 假设我们有多张卡,这里指定用第一张
                  capabilities: [gpu]
        command: ["--model", "meta-llama/Llama-2-7b-chat-hf"]
        # 注意:我们删除了 ports 映射,因为它们不再需要直接对外服务
        restart: always
        # 为容器指定一个固定的、可预测的主机名,方便 Nginx 寻址
        hostname: vllm-1
    
      vllm-2:
        image: vllm/vllm-openai:latest
        deploy:
          resources:
            reservations:
              devices:
                - driver: nvidia
                  count: 1 # 指定用第二张卡(如果可用)
                  capabilities: [gpu]
        command: ["--model", "meta-llama/Llama-2-7b-chat-hf"]
        restart: always
        hostname: vllm-2
        # 让 vllm-2 依赖于 vllm-1,可以控制启动顺序(可选)
        depends_on:
          - vllm-1
    
      vllm-3:
        image: vllm/vllm-openai:latest
        deploy:
          resources:
            reservations:
              devices:
                - driver: nvidia
                  count: 1 # 指定用第三张卡(如果可用)
                  capabilities: [gpu]
        command: ["--model", "meta-llama/Llama-2-7b-chat-hf"]
        restart: always
        hostname: vllm-3
        depends_on:
          - vllm-2
    

    注意: 上述配置假设你有3张GPU。如果只有1张,count: 1 会导致后两个服务无法启动。对于单GPU服务器,部署多个vLLM实例意义不大,因为它们会争抢同一个物理资源。这里的核心是展示“水平扩展”的思想,在生产环境中,这3个服务很可能会部署在3台不同的物理服务器上。为了在本教程中模拟,你可以暂时将 deploy 部分注释掉,让它们共享GPU资源(性能会受影响,但架构可以跑通)。

    现在,我们有了三个整装待发的“大脑”,但用户并不知道该找谁。接下来,我们需要一位“交通指挥官”登场。

2.2 负载均衡层:聪明的“交通指挥官” (Nginx)

Nginx 是一个高性能的 Web 服务器和反向代理服务器。在这里,它扮演的角色就是反向代理和负载均衡。

  • 为什么需要 Nginx?

    1. 统一入口:所有用户只访问 Nginx 的地址,Nginx 负责将请求转发到后面的某个 vLLM 实例。用户无需关心后端有多少台服务器,也无需知道它们的具体地址。
    2. 负载均衡:当一个请求过来时,Nginx 会根据我们设定的策略(如轮询、最少连接数等),把它公平地分发给后端健康的 vLLM 实例,避免了某个实例被“撑死”而其他实例在“围观”的情况。
    3. 健康检查:Nginx 可以自动检查后端服务是否存活,如果某个 vLLM 实例宕机了,Nginx 会自动停止向它转发请求,从而实现故障的自动隔离。
  • 配置实战
    首先,在项目根目录下创建一个 nginx 文件夹,并在其中创建一个 nginx.conf 文件。

    mkdir nginx
    touch nginx/nginx.conf
    

    然后,编辑 nginx/nginx.conf 文件,填入以下内容:

    # nginx.conf
    
    # events 块定义了 Nginx 的工作模式和连接数上限
    events {
        worker_connections 1024; # 每个工作进程允许的最大连接数
    }
    
    # http 块是配置 HTTP 服务器的主要部分
    http {
        # 定义一个名为 'vllm_backend' 的上游服务器集群
        upstream vllm_backend {
            # 这里列出了我们所有的 vLLM 服务实例
            # 'vllm-1:8000' -> vllm-1 是我们在 docker-compose.yml 中定义的服务名
            # Docker 的内部 DNS 会自动将服务名解析为容器的 IP 地址
            server vllm-1:8000;
            server vllm-2:8000;
            server vllm-3:8000;
        }
    
        # 定义一个虚拟主机
        server {
            # 监听 80 端口,这是 HTTP 的标准端口
            listen 80;
    
            # 定义根路径 '/' 的请求处理逻辑
            location / {
                # proxy_pass 是反向代理的核心指令
                # 它告诉 Nginx 将匹配到的请求转发到 'vllm_backend' 集群
                proxy_pass http://vllm_backend;
                
                # 其他一些代理相关的头部设置,以确保后端服务能获取到真实的客户端信息
                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;
            }
        }
    }
    
  • 整合到 Docker Compose
    现在,把 Nginx 服务也加入到我们的 docker-compose.yml 中:

    # docker-compose.yml (版本3)
    version: '3.8'
    
    services:
      # Nginx 服务
      nginx:
        image: nginx:latest
        ports:
          # 将宿主机的 80 端口映射到 Nginx 容器的 80 端口
          # 这是我们整个服务的唯一入口
          - "80:80"
        volumes:
          # 将我们本地的 nginx.conf 文件挂载到容器内部对应的路径
          # 这样 Nginx 容器启动时就会加载我们的自定义配置
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro # 'ro' 表示只读
        depends_on:
          # 确保 Nginx 在所有 vLLM 服务启动之后再启动
          - vllm-1
          - vllm-2
          - vllm-3
        restart: always
    
      vllm-1:
        # ... (vllm-1 的配置保持不变)
        image: vllm/vllm-openai:latest
        hostname: vllm-1
        # ...
    
      vllm-2:
        # ... (vllm-2 的配置保持不变)
        image: vllm/vllm-openai:latest
        hostname: vllm-2
        # ...
    
      vllm-3:
        # ... (vllm-3 的配置保持不变)
        image: vllm/vllm-openai:latest
        hostname: vllm-3
        # ...
    

    现在,我们的架构已经初具雏形。在终端执行 docker-compose up -d --build (添加 --build 以确保 Nginx 使用最新配置)。

    启动成功后,再次使用 curl 命令,但这次我们访问的是 Nginx 的 80 端口:

    # 注意 URL 变了,端口是 80 (可以省略)
    curl http://localhost/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "meta-llama/Llama-2-7b-chat-hf",
        "prompt": "Tell me a joke",
        "max_tokens": 20
    }'
    

    你可以连续执行这个命令几次。虽然表面上看起来结果都一样,但实际上 Nginx 正在后台默默地将你的请求轮流发送给 vllm-1, vllm-2, 和 vllm-3。你可以通过查看各个 vLLM 容器的日志来验证这一点:

    docker logs llm-arch-project-vllm-1-1
    docker logs llm-arch-project-vllm-2-1
    docker logs llm-arch-project-vllm-3-1
    

    你会发现请求日志交替出现在这三个容器中。

2.3 缓存层:给你的服务装上“高速内存” (Redis)

即使我们有了多个 vLLM 实例,但对于一些高频、重复的请求(比如“你好”、“今天天气怎么样?”),每次都让 GPU 去计算是一种巨大的浪费。缓存就是解决这个问题的利器。

  • 为什么需要缓存?
    Redis 是一个基于内存的高性能键值数据库。把它引入我们的架构,可以将计算过一次的结果“存起来”。当同样的请求再次到来时,我们的应用可以先问一下 Redis:“嘿,这个问题你见过吗?”如果 Redis 说:“见过,答案是这个”,我们就可以直接返回答案,无需惊动背后宝贵的 GPU 资源。这能带来两个巨大的好处:

    1. 极速响应:从内存中读取数据比 GPU 计算快几个数量级。
    2. 降低成本:减少了对 GPU 的调用,就等于节省了计算资源和电费。
  • 配置实战
    我们在 docker-compose.yml 中添加 Redis 主从服务。一个主节点(master)负责写操作,一个或多个从节点(slave)负责读操作并同步主节点的数据,当主节点宕机时,从节点可以被提升为新的主节点,从而实现高可用。

    # docker-compose.yml (版本4)
    version: '3.8'
    
    services:
      nginx:
        # ... (Nginx 配置不变)
    
      vllm-1:
        # ... (vLLM 配置不变)
      vllm-2:
        # ...
      vllm-3:
        # ...
    
      # Redis 主节点
      redis-master:
        image: redis:latest
        hostname: redis-master
        ports:
          # 为了方便调试,我们暴露主节点的端口
          - "6379:6379"
        restart: always
    
      # Redis 从节点
      redis-slave:
        image: redis:latest
        hostname: redis-slave
        # 启动命令,告诉这个实例去复制 redis-master 的数据
        command: redis-server --slaveof redis-master 6379
        depends_on:
          - redis-master
        restart: always
    

    再次运行 docker-compose up -d 启动 Redis 服务。

  • 逻辑集成
    如何让我们的服务使用缓存呢?这通常是在应用逻辑层面实现的。虽然 vLLM 服务本身不直接操作 Redis,但调用 vLLM 的上一层服务(可以理解为一个简单的 API 网关或者你的业务后端)会实现这个逻辑。

    下面是一段 Python 伪代码,展示了这个逻辑流程:

    # 这段代码并不直接运行,而是展示集成 Redis 的核心思想
    import redis
    import requests
    import json
    
    # 连接到我们在 Docker Compose 中定义的 Redis 主节点
    redis_client = redis.Redis(host='localhost', port=6379, db=0)
    
    def get_llm_completion(prompt: str):
        # 1. 生成一个基于 prompt 的唯一 key
        cache_key = f"llm_cache:{prompt}"
    
        # 2. 尝试从 Redis 获取缓存
        cached_result = redis_client.get(cache_key)
        if cached_result:
            print("命中缓存!从 Redis 返回结果。")
            return json.loads(cached_result)
    
        # 3. 如果缓存未命中,则请求我们的 LLM 服务 (通过 Nginx)
        print("未命中缓存,正在请求 LLM 服务...")
        response = requests.post(
            "http://localhost/v1/completions",
            json={
                "model": "meta-llama/Llama-2-7b-chat-hf",
                "prompt": prompt,
                "max_tokens": 50
            }
        )
        result = response.json()
    
        # 4. 将新结果存入 Redis,并设置过期时间(例如1小时)
        redis_client.setex(cache_key, 3600, json.dumps(result))
    
        return result
    
    # --- 测试 ---
    # 第一次调用
    print(get_llm_completion("What is the capital of France?"))
    # 第二次调用(你会看到 "命中缓存!" 的提示)
    print(get_llm_completion("What is the capital of France?"))
    

    通过这个流程,我们的系统变得更加智能和高效。至此,我们架构的核心骨架已经搭建完毕。接下来,我们要为它装上“眼睛”和“神经系统”,让它变得可监控、更健壮。


第三部分:健壮性与可观测性 (Chapter 3: Making It Robust and Observable)

一个没有监控的系统就像在夜间闭着眼睛开车,极其危险。我们需要知道系统内部发生了什么,才能在问题扩大之前发现并解决它。

3.1 监控层:开启“上帝视角” (Prometheus + Grafana)
  • 为什么需要监控?

    • Prometheus: 一个开源的监控和告警工具。它会定期地从我们的各个服务(Nginx, vLLM 等)那里“拉取”状态指标数据,并存储起来。
    • Grafana: 一个开源的可视化平台。它可以连接到 Prometheus,将那些原始的、数字化的指标数据,变成人类易于理解的、漂亮的图表和仪表盘。

    通过它们,我们可以实时看到诸如“当前 QPS 是多少?”、“请求的平均响应时间是多少?”、“GPU 使用率是否过高?”等关键问题的答案。

  • 配置实战

    1. 准备 Prometheus 配置文件
      在项目根目录下创建 prometheus 文件夹及配置文件 prometheus.yml
      mkdir prometheus
      touch prometheus/prometheus.yml
      
      编辑 prometheus/prometheus.yml
      # prometheus.yml
      global:
        scrape_interval: 15s # 每 15 秒抓取一次指标
      
      scrape_configs:
        # 抓取 vLLM 的指标
        # vLLM 镜像内置了 Prometheus exporter,默认在 8000 端口的 /metrics 路径暴露指标
        - job_name: 'vllm'
          static_configs:
            - targets: ['vllm-1:8000', 'vllm-2:8000', 'vllm-3:8000']
      
    2. 将 Prometheus 和 Grafana 添加到 Docker Compose
      # docker-compose.yml (版本5)
      version: '3.8'
      
      services:
        # ... (nginx, vllms, redis services)
      
        prometheus:
          image: prom/prometheus:latest
          volumes:
            - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
          command:
            - '--config.file=/etc/prometheus/prometheus.yml'
          ports:
            - "9090:9090" # Prometheus UI 端口
          restart: always
      
        grafana:
          image: grafana/grafana-oss:latest
          ports:
            - "3000:3000" # Grafana UI 端口
          depends_on:
            - prometheus
          restart: always
      
    3. 启动并配置
      运行 docker-compose up -d。启动后:
      • 访问 http://localhost:9090,这是 Prometheus 的界面,你可以在 Status -> Targets 中看到它是否成功连接到了我们的 vLLM 实例。
      • 访问 http://localhost:3000,这是 Grafana 的界面。默认用户名和密码都是 admin,首次登录会提示修改密码。
      • 配置 Grafana:
        1. 添加数据源: 登录后,点击左侧齿轮图标 Configuration -> Data Sources -> Add data source -> 选择 Prometheus。在 URL 字段,填入 http://prometheus:9090 (因为 Grafana 和 Prometheus 在同一个 Docker 网络中,可以直接使用服务名访问),然后点击 Save & Test
        2. 创建仪表盘: 点击左侧加号图标 Create -> Dashboard -> Add new panel。在 Metrics browser 中,你可以输入 vLLM 暴露的指标,例如 vllm_requests_processing_total,就可以看到正在处理的请求总数。你可以自己组合图表,或者去 Grafana 官网仪表盘市场 寻找现成的 vLLM 仪表盘模板并导入。
3.2 容灾设计:当“灾难”来临时

高可用性意味着系统能够抵御部分组件的失败,而整体服务不受影响。

  • 推理服务容灾
    我们在 docker-compose.yml 中为所有 vLLM 服务配置了 restart: always。这已经为我们提供了一层最基础的容灾。来模拟一次故障:

    1. 找到一个 vLLM 容器的 ID:docker ps
    2. 手动停止它:docker stop <容器ID>
    3. 你会发现,Nginx 仍然在正常工作,只是暂时不会把请求发给那个“消失”的实例。
    4. 几秒钟后,再次运行 docker ps,你会看到一个新的同名容器已经被 Docker 自动启动了,它会重新加入服务集群。整个过程对用户来说是无感的。
  • 缓存层容灾
    我们在 docker-compose.yml 中配置了 Redis 的主从复制。redis-slave 会实时备份 redis-master 的所有数据。如果 redis-master 容器宕机:

    • 在我们的简单配置中,写操作会失败,但读操作可能仍由从节点服务(取决于客户端配置)。
    • 在真正的生产环境中,会引入一个名为 Redis Sentinel (哨兵) 的组件。哨兵集群会监控主从节点的状态,一旦发现主节点宕机,它们会自动进行“投票”,将一个从节点提升为新的主节点,并通知应用方更新连接地址。这个过程是全自动的,能确保缓存服务在数秒内恢复写入功能。在 Docker Compose 中实现哨兵模式较为复杂,但理解这个概念是关键。

第四部分:实战演练与成本优化 (Chapter 4: Battle Testing and Optimization)

架构搭好了,是骡子是马,得拉出来遛遛。

4.1 压力测试:模拟十万用户的“围攻” (JMeter)

我们将使用 JMeter 来模拟大量用户并发访问我们的服务。
注意:模拟十万并发需要非常强大的压测机和目标服务器。在本教程中,我们以 1000 并发为例进行演示,其原理和方法完全相同。架构本身具备扩展到更高并发的能力。

  • 创建测试计划

    1. 打开 JMeter。
    2. 右键点击“测试计划” -> 添加 -> 线程(用户) -> 线程组
      • 线程数: 设为 1000 (模拟 1000 个并发用户)。
      • Ramp-Up 时间(秒): 设为 10 (在10秒内让这1000个用户全部启动)。
      • 循环次数: 选择“永远”。
    3. 右键点击“线程组” -> 添加 -> 取样器 -> HTTP请求
      • 服务器名称或IP: localhost (或你的云服务器 IP)。
      • 端口号: 80
      • 方法: POST
      • 路径: /v1/completions
      • Body Data 中,填入我们的 JSON 请求体。
    4. 右键点击“线程组” -> 添加 -> 监听器 -> 聚合报告查看结果树
  • 执行测试与分析报告

    1. 点击 JMeter 工具栏的绿色启动按钮。
    2. 观察 JMeter:
      • 聚合报告: 关注 Throughput (吞吐量/QPS),这是衡量系统处理能力的核心指标。同时关注 Error %,理想情况下应该是 0。Average99% Line (P99 延迟) 则反映了请求的响应速度。
    3. 观察 Grafana: 在压测的同时,打开你的 Grafana 仪表盘。你会看到 QPS 图表飙升,GPU/CPU 使用率曲线陡然升高,请求延迟图表也会有相应的波动。这才是我们监控系统最有价值的时刻!
    4. 瓶颈分析:
      • 如果压测时,你发现 GPU 使用率持续在 95% 以上,而吞吐量上不去了,说明 瓶颈在 GPU 计算能力。解决方案:增加更多的 vLLM 实例(或使用更强的 GPU)。
      • 如果 GPU 使用率不高,但 Nginx 的 CPU 占用率很高,或网络出入带宽跑满,说明 瓶颈可能在网络层或负载均衡器
      • 如果错误率很高,需要去查看 vLLM 或 Nginx 的日志,定位是连接超时还是服务内部错误。
  • 从十万到百万并发
    我们当前的架构是单机部署。要支撑百万并发,必须进行 水平扩展 (Scale Out)。这意味着你需要增加更多的服务器,每台服务器上都运行着我们这套 docker-compose 服务。然后,在所有这些 Nginx 前面,再加一层更高性能的负载均衡器(如云服务商提供的 SLB/ELB,或者自建的 LVS 集群),将流量分发到不同的服务器节点上。原理与我们单机内的 Nginx 分发流量给 vLLM 完全一样,只是层级更高。

4.2 成本控制:如何让你的钱包不“失血”

GPU 服务器非常昂贵,让一个庞大的集群 24 小时全速运行,成本是惊人的。智能的成本控制是生产环境的必备技能。

  • 动态扩缩容思想
    核心思想是“弹性”。服务的实例数量不应该是固定的,而应像弹簧一样,根据负载(流量)动态变化。

    • 高峰期 (如白天工作时间): 用户请求多,自动增加 vLLM 实例数量(扩容,Scale Out),确保服务质量。
    • 低谷期 (如深夜): 用户请求少,自动减少 vLLM 实例数量(缩容,Scale In),节省服务器成本。
  • 实现思路
    Docker Compose 本身不具备自动扩缩容的能力。这是更专业的容器编排工具的领域,例如 Kubernetes
    在 Kubernetes 中,可以轻松实现这一点:

    1. 将我们的 vLLM 服务定义为一个 Deployment
    2. 创建一个 HorizontalPodAutoscaler (HPA) 对象。
    3. 在 HPA 中定义扩缩容规则,例如:“当 vLLM 服务的平均 GPU 使用率(通过 Prometheus 监控获得)连续 5 分钟超过 70% 时,自动增加实例数量,上限为 20 个。当使用率低于 30% 时,自动减少实例,下限为 2 个。
      Kubernetes 会自动完成剩下的一切。云服务商(AWS, GCP, Azure)也提供类似的弹性伸缩组(Auto Scaling Group)功能。
  • 成本估算示例
    假设一台 GPU 服务器每小时成本为 2 美元。

    • 固定数量策略: 部署 10 台服务器,24小时运行。成本 = 10 * 2 * 24 = 480 美元/天
    • 弹性策略: 平均每天 8 小时高峰期需要 10 台,另外 16 小时低谷期只需要 2 台。成本 = (10 * 2 * 8) + (2 * 2 * 16) = 160 + 64 = 224 美元/天
      仅通过简单的弹性伸缩,成本就下降了超过 50%。

第五部分:总结与展望
5.1 我们的成果:回顾完整的架构

我们从一个孤零零的 vLLM 实例出发,通过层层加固,最终构建了一个健壮、高效、可观测的高可用架构。让我们最后看一下完整的关键配置文件。

  • 最终 docker-compose.yml

    # 包含了 nginx, 3个vllm实例, redis主从, prometheus, grafana 的完整配置
    version: '3.8'
    services:
      nginx: # ...
      vllm-1: # ...
      vllm-2: # ...
      vllm-3: # ...
      redis-master: # ...
      redis-slave: # ...
      prometheus: # ...
      grafana: # ...
    
  • 最终 nginx.conf

    # 包含了 upstream 和 server 配置
    events { #... }
    http {
        upstream vllm_backend { #... }
        server { #... }
    }
    
  • 一个请求的旅程(序列图)

    用户 负载均衡器 应用逻辑 (带缓存) 缓存 推理服务 发起请求 (POST /v1/completions) 转发请求 查询缓存 (GET cache_key) 返回缓存结果 返回结果 返回最终响应 (内部)选择一个vLLM实例 转发请求到具体实例 GPU计算后返回结果 返回结果 写入新缓存 (SETEX cache_key) 返回结果 返回最终响应 alt [缓存命中] [缓存未命中] 用户 负载均衡器 应用逻辑 (带缓存) 缓存 推理服务

您可能感兴趣的与本文相关的镜像

Vllm-v0.11.0

Vllm-v0.11.0

Vllm

vLLM是伯克利大学LMSYS组织开源的大语言模型高速推理框架,旨在极大地提升实时场景下的语言模型服务的吞吐与内存使用效率。vLLM是一个快速且易于使用的库,用于 LLM 推理和服务,可以和HuggingFace 无缝集成。vLLM利用了全新的注意力算法「PagedAttention」,有效地管理注意力键和值

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

THMAIL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值