前后端项目上线部署实践(AI 网关 Docker + Nginx 流程)

企业级 AI 网关这种 Spring Boot 后端加 Vue 前端分离架构的项目,上线的核心不是把代码跑起来,而是把 Nginx 反向代理、SSE 长连接、多模型服务调用这三条链路在服务器上一次性打通。我们项目采用「后端 jar 包 + systemd 守护」与「前端 dist 静态资源 + Nginx 托管」的组合,中间用同一个 Nginx 做流量入口,把 /api 转发到后端、把静态资源直出,并在网关层关闭响应缓冲以保证对话流式输出不卡顿。这套流程从准备环境到可回滚发布,完整走下来大约分七个阶段,下面按实战顺序展开。

一、上线前的环境与物料准备

部署动作本身只占整个上线过程的一小部分,真正的体力活在环境一致性。先做两件事:锁定运行时版本,准备生产配置。

  1. 服务器统一装 OpenJDK 17 与 Node.js 18 LTS,本地开发环境与线上保持一致,避免编译版本与运行时版本错位;
  2. 把数据库、Redis、上游模型服务的连接串抽进 application-prod.yml,密码通过环境变量注入,不写死在仓库里;
  3. 在安全组放行 80、443 与 SSH 端口,后端端口不出现在公网,只允许内网访问。

生产配置里最容易漏的是时区与连接池参数。网关要访问多个模型服务,超时与连接数直接影响吞吐:

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://10.0.0.8:3306/ai_gateway?useUnicode=true&characterEncoding=utf8
  data:
    redis:
      host: 10.0.0.9
      port: 6379
ai:
  model:
    openai:
      base-url: ${OPENAI_BASE_URL}
      api-key: ${OPENAI_API_KEY}
    qwen:
      api-key: ${QWEN_API_KEY}

二、后端打包与进程守护

后端用 Maven 打成可执行 fat-jar,内嵌 Tomcat,不需要额外装应用服务器。打包命令固定为:

mvn clean package -DskipTests

产物 target/ai-gateway.jar 上传到 /opt/ai-gateway/。直接 nohup java -jar 跑起来也能用,但终端一关、服务器一重启,进程就没了。生产环境用 systemd 托管,崩溃自动拉起,开机自启,日志归 journald 统一管理:

[Unit]
Description=AI Gateway Backend
After=network.target mysqld.service redis.service

[Service]
User=gateway
WorkingDirectory=/opt/ai-gateway
ExecStart=/usr/bin/java -Xms1g -Xmx2g -jar /opt/ai-gateway/ai-gateway.jar --spring.profiles.active=prod
Restart=always
RestartSec=5
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

JVM 堆大小按并发连接数调整。网关持有 SSE 长连接,连接对象本身占内存不高,但请求线程与异步任务执行器要留足余量,Restart=always 加上健康检查脚本,可以在进程假死时兜底。

三、前端构建与 Nginx 托管

前端是 Vue 项目,打包前把 API 基地址改到同域相对路径 /api,由 Nginx 统一转发,规避跨域问题。构建命令:

npm run build

产物 dist/ 上传到 /var/www/ai-gateway-web/,权限收给部署用户。Nginx 站点配置承担两件事:静态资源直出与 API 反代。

server {
    listen 80;
    server_name gateway.example.com;

    root /var/www/ai-gateway-web;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

try_files 回退到 index.html 是 Vue 路由的 history 模式必需配置,否则刷新二级页面直接 404。

四、SSE 流式接口的反向代理专项配置

这一步是 AI 网关部署与普通 CRUD 项目的关键差异点。对话接口走 SSE,响应体是边生成边推送的事件流。Nginx 默认开启响应缓冲,会把上游数据攒到一定量才发给客户端,表现就是聊天窗口”憋半天出一大段”,和逐字流出的预期完全相反。

在 /api/ 的 location 里追加三条:

location /api/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 300s;
    proxy_set_header X-Accel-Buffering no;
    add_header Cache-Control "no-cache";
}

同时后端在响应头里显式声明 Content-Type: text/event-stream 与 Cache-Control: no-cache,双重保证事件不被中间层缓存。proxy_read_timeout 从默认 60 秒调大到 300 秒,因为大模型生成长文时单次间隔可能超过 60 秒,心跳包也能在这个维度兜底。

流式事件格式也要在生产环境复查一遍:每条事件以 data: 开头、空行结尾,结束标记统一用 [DONE],异常时先推一条错误事件再关连接。这些约定在开发环境写代码时就已固定,上线时重点确认代理层没有改动事件内容。另外网关接入了多家模型服务,出站请求需要走代理或加白名单,部署时把各家的域名与证书一并配好,避免线上环境 DNS 或证书校验不一致导致首次调用失败。

五、HTTPS 与上线前自检

生产环境一律上 HTTPS。证书用 Let’s Encrypt 签的免费 DV 证书,Certbot 一键签发并配置自动续期,每周一凌晨执行一次续期任务。443 端口上同时挂好前端页面与 API 反代,80 端口 301 跳转。

上线前自检按这个顺序执行:

  1. 后端单独验证:curl -i http://127.0.0.1:8080/actuator/health 返回 UP;
  2. SSE 链路验证:用 curl -N 直连后端流式接口,确认事件按 data: 分片逐条到达;
  3. 经过 Nginx 复测同上,确认缓冲已关闭、事件实时透传;
  4. 前端页面走 HTTPS 访问,登录、对话、历史记录三块功能逐项过一遍;
  5. 观察日志与内存曲线,确认无 OOM 风险后把流量完整切过来。

六、三种部署方式的取舍

同一个项目,我们评估过三套部署方案,最终选了 systemd 直跑,理由写在表格里:

部署方式 上手成本 运维能力 资源占用 适用阶段
nohup 后台运行 最低 弱,重启即失联 低 联调、试运行
systemd 服务托管 低 中,支持自启与崩溃拉起 低 单机生产
Docker 容器化 中 强,配合 Compose 编排多服务 中 多服务、迁移频繁

当前网关服务数量有限,单机 systemd 足够。如果后续把网关、MySQL、Redis 全部容器化并引入 CI/CD,再整体切到 Docker Compose,部署脚本一次性写好。

七、发布与回滚

发布用「蓝绿切换」思路:新 jar 先以 --server.port=8081 起在旁路,压测与冒烟通过后,改 Nginx 的 proxy_pass 指向 8081,观察十分钟无异常再停掉旧进程。回滚就是改回 8080,全程不动数据库结构,秒级完成。前端同理,dist 目录用时间戳目录存放旧版本,发布失败时改 Nginx 的 root 指向即可回退。

压测环节专门模拟了并发对话场景:用脚本同时发起 30 路 SSE 连接,重点看两个指标,一是事件到达间隔是否稳定,二是后端线程池与连接数是否被打满。压测期间出现过一次 Tomcat 默认线程池耗尽导致新连接排队的问题,后来把异步任务执行器换成有界队列并设置合理拒绝策略,才把并发连接数提上去。这类问题只会在流式长连接场景暴露,普通接口压测测不出来。

常见问题(FAQ)

Q1:前端刷新页面报 404 是什么原因?

Vue history 路由在 Nginx 缺少 try_files 回退规则,补上 try_files $uri $uri/ /index.html 即修复。

Q2:SSE 接口不流式、一次性返回怎么办?

Nginx 默认缓冲响应,在 location 里加 proxy_buffering off 并设置 X-Accel-Buffering: no。

Q3:服务重启后端口被占用怎么处理?

先 netstat -tlnp 定位占用进程,确认是残留旧进程后停掉,再启动新服务。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
赵其鑫的头像赵其鑫管理团队

相关推荐

返回顶部