Docker + Nginx 实现 Java 服务零停机发布并解决发布502问题
本文为生产环境真实踩坑总结,通用适用于:SpringBoot / 单体服务 / Docker Compose + Nginx 架构,无 Kubernetes 也能实现低成本高可用发布。
一、前言
在传统服务器部署中,我们更新后端服务通常是:
- 停止旧容器
- 启动新容器
这个过程会直接导致:
- 发布期间接口 502 Bad Gateway
- 用户访问中断、前端报错
- 监控告警、接口超时失败
即使使用 “先启动临时实例、再关闭旧实例”,如果 Nginx 配置、健康检查、脚本逻辑 不正确,依然会出现各种诡异问题:发布瞬间 502、脚本卡死、接口不通等。
本文带你从零搭建一套真正可用、无感知、零停机的发布方案
二、遇到的典型问题(你大概率也踩过)
直接重启容器 → 必现 502
nginx 502 Bad Gateway
原因:主容器重启期间,Nginx 仍在转发请求,无可用后端服务。
加了 backup 副本 → 依然偶尔 502
upstream ring_servers {
server ring:8080;
server ring_temp:8080 backup;
}
原因:
- backup 只有主节点完全挂掉才会切换
- Nginx 没有失败重试机制,重启瞬间仍会丢请求
- 健康检查不及时
发布脚本检查接口 → 直接卡死
curl 检查接口返回 000,脚本不动了
原因:
- 接口未就绪时,curl 阻塞等待
- 检查公网入口受 Nginx 影响,无法真实判断服务状态
健康检查接口被登录拦截 → 返回 401
原因:健康接口走了登录校验,HTTP 200 但业务码 401,导致脚本误判。
三、整体零停机发布方案(通用架构)
核心思路:双实例兜底 + Nginx 自动重试 + 安全发布脚本
- 正常运行:主服务提供流量
- 发布时:先启动临时副本 → 等待就绪 → 重启主服务 → 主服务就绪 → 关闭临时副本
- Nginx:自动转发到可用节点,用户无感知
四、第一步:Nginx 配置(最关键,解决 502)
- upstream 负载均衡配置
upstream ring_servers {
least_conn;
# 主服务
server ring:8080 max_fails=1 fail_timeout=1s;
# 临时服务(发布兜底,backup 模式)
server ring_temp:8080 max_fails=3 fail_timeout=30s backup;
# 核心:出错自动重试下一个节点,用户看不到 502
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 2;
keepalive 32;
}
- 代理通用配置
proxy_connect_timeout 10s; proxy_send_timeout 10s; proxy_read_timeout 30s; proxy_buffering on;
说明:proxy_next_upstream 是零停机的灵魂,请求失败会自动重试下一个节点。
五、第二步:docker-compose.yml 配置
只保留核心结构,可直接套用:
version: "3.9"
services:
# 主服务
ring:
image: ring:1.0.0
restart: always
ports:
- "8080:8080"
networks:
- webnet
healthcheck:
test: ["CMD", "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "http://localhost:8080/api/health"]
interval: 5s
timeout: 3s
retries: 10
start_period: 60s
# 临时服务(发布用)
ring_temp:
image: ring:1.0.0
restart: "no" # 不自动重启
ports:
- "8081:8080"
networks:
- webnet
networks:
webnet:
driver: bridge六、第三步:生产可用零停机发布脚本
重要说明
脚本中使用的接口:
/api/app/checkServerStatus
这只是一个样例接口,你可以替换为自己项目中能判断项目正常运行的任意接口,例如:
- /actuator/health
- /api/health
- /system/health
只要该接口能在服务启动完成后返回 HTTP 200 即可。
零停机发布脚本(最终版)
#!/bin/bash
set +e
# ==================== 配置项(根据自己项目修改) ====================
# 公网要监控的接口(验证用户是否能正常访问)
MONITOR_URL="https://你的域名/api/app/checkServerStatus"
# 主服务端口检查(不经过 Nginx)
MAIN_CHECK_URL="http://127.0.0.1:8080/api/app/checkServerStatus"
# 临时服务端口检查
TEMP_CHECK_URL="http://127.0.0.1:8081/api/app/checkServerStatus"
MAX_RETRY=30
RETRY_INTERVAL=5
# ====================================================================
# 后台实时监控公网接口
start_monitor() {
while true; do
current_time=$(date "+%Y-%m-%d %H:%M:%S")
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --insecure $MONITOR_URL 2>/dev/null)
if [ "$HTTP_CODE" = "200" ]; then
echo -e "\033[32m[${current_time}] ✅ 公网接口正常:200\033[0m"
else
echo -e "\033[31m[${current_time}] ❌ 公网接口异常:${HTTP_CODE}\033[0m"
fi
sleep 1
done
}
# 检查端口服务是否就绪
check_service_ready() {
local url=$1
local desc=$2
echo -e "\033[36m=== 等待 ${desc} 就绪 ===\033[0m"
local retry=0
while [ $retry -lt $MAX_RETRY ]; do
code=$(curl -s -o /dev/null -w "%{http_code}" --insecure $url 2>/dev/null)
if [ "$code" = "200" ]; then
echo -e "\033[32m✅ ${desc} 启动成功\033[0m"
return 0
fi
retry=$((retry+1))
echo "重试 ${retry}/${MAX_RETRY},状态码:$[code],等待 ${RETRY_INTERVAL}s..."
sleep $RETRY_INTERVAL
done
echo -e "\033[31m❌ ${desc} 启动超时\033[0m"
kill $MONITOR_PID 2>/dev/null
exit 1
}
# ==================== 发布主流程 ====================
echo -e "\n==================== 开始零停机发布 ====================\n"
# 1. 启动后台监控
start_monitor &
MONITOR_PID=$!
# 2. 启动临时副本
echo -e "\n1. 启动临时服务"
docker compose up -d ring_temp
check_service_ready "$TEMP_CHECK_URL" "临时服务"
# 3. 重启主服务
echo -e "\n2. 重启主服务"
docker compose up -d --force-recreate ring
check_service_ready "$MAIN_CHECK_URL" "主服务"
# 4. 关闭临时服务
echo -e "\n3. 关闭临时服务"
docker compose stop ring_temp
docker compose rm -f ring_temp
# 5. 停止监控
kill $MONITOR_PID 2>/dev/null
sleep 1
echo -e "\n\033[32m=====================================================\033[0m"
echo -e "\033[32m✅ 发布完成!全程无 502,用户无感知\033[0m"
echo -e "\033[32m=====================================================\033[0m"
exit 0七、第四步:使用与验证
- 赋予执行权限
chmod +x deploy_ring.sh
- 执行发布
./deploy_ring.sh
- 观察效果
你会看到:
公网接口全程输出 ✅ 200
主服务重启期间无任何 502
发布完成后自动关闭临时副本
用户完全无感知
八、核心避坑总结(非常重要)
- 必须先启动临时副本,再重启主服务
- Nginx 必须配置 proxy_next_upstream,这是无 502 的关键
- 健康检查不要检查公网入口,直接检查容器端口更稳定
- 临时服务使用 backup 模式,只做兜底不做负载,避免启动期间被流量打挂
- 健康接口尽量免登录,只返回 HTTP 200 即可,不要做业务校验
- 脚本中 /api/app/checkServerStatus只是示例,替换成你自己的健康检查接口
九、适用场景
- SpringBoot 后端服务
- 单体应用 / 无微服务架构
- Docker Compose 部署
- 中小企业服务器、无 K8s 环境
- 追求低成本、稳定、无感知发布
到此这篇关于Docker + Nginx 实现 Java 服务零停机发布并解决发布502问题的文章就介绍到这了,更多相关Docker Nginx Java 服务零停机发布内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
docker-compose网络设置之networks的使用
本文详细解释了在使用 Docker Compose时如何配置网络,包括创建、使用和问题解决等方面,介绍了如何通过docker-compose.yml文件快速编排和部署应用服务,同时解决网络隔离问题,感兴趣的可以了解一下2024-10-10


最新评论