单服务器 spring cloud + nacos 灰度部署,nginx shell脚本
·
核心流程
nacos 配置
新旧服务同时运行
spring:
cloud:
gateway:
routes:
# 规则1:灰度路由 - 当请求头 X-Gray-User 存在时,转发至灰度版本
- id: blog-gray-route
uri: http://127.0.0.1:9092
predicates:
- Path=/api/manage/**
- Header=X-Gray-User, .+ # 正则匹配:只要 Header 存在且非空
filters:
- StripPrefix=1
- AddRequestHeader=gray-version, v2.0 # 可选:为下游服务添加灰度标识
# 规则2:默认路由 - 所有其他请求,转发至稳定版本
- id: blog-default-route
uri: http://127.0.0.1:9091
predicates:
- Path=/api/manage/**
filters:
- StripPrefix=1
只剩新服务运行
spring:
cloud:
gateway:
routes:
# 规则1:灰度路由 - 当请求头 X-Gray-User 存在时,转发至灰度版本
- id: blog-gray-route
uri: http://127.0.0.1:9092
predicates:
- Path=/api/manage/**
filters:
- StripPrefix=1
- AddRequestHeader=gray-version, v2.0 # 可选:为下游服务添加灰度标识
如果你要路由中还保留旧服务,只是不走流量
spring:
cloud:
gateway:
routes:
# 规则1:100% 流量走这个路由
- id: blog-gray-route
uri: http://127.0.0.1:9092
predicates:
- Path=/api/manage/**
filters:
- StripPrefix=1
- AddRequestHeader=gray-version, v2.0 # 可选:为下游服务添加灰度标识
- Weight=group1, 100 # 100% 流量走这个路由
# 规则2:0% 流量
- id: blog-default-route
uri: http://127.0.0.1:9091
predicates:
- Path=/api/manage/**
filters:
- StripPrefix=1
- Weight=group1, 0 # 0% 流量
shell
目的:新启动的程序,只会替换最新时间的那个程序。
旧程序的关闭,是监听 元数据 autoclose=true 时,自动关闭。
#!/bin/bash
# === 配置区域 ===
PORT1=9091
PORT2=9092
APP_BASE=blog
APP_PATH=/data/website/blog
JAVA_HOME=/data/jdk-25/bin/java
# 生成带端口的 jar 文件名
get_jar_name() {
echo "${APP_BASE}.$1.jar"
}
# === 工具函数 ===
# 检查指定端口的进程是否存在,返回 PID
is_port_running() {
local port=$1
local jar_name=$(get_jar_name $port)
pid=$(ps -ef | grep "$jar_name" | grep -v grep | awk '{print $2}')
if [ -z "$pid" ]; then
return 1
else
echo $pid
return 0
fi
}
# 获取进程的启动时间戳(秒)
get_start_time() {
local pid=$1
local lstart=$(ps -p $pid -o lstart= 2>/dev/null)
if [ -n "$lstart" ]; then
date -d "$lstart" +%s 2>/dev/null
else
echo 0
fi
}
# 获取当前运行的端口及 PID(如果两个都运行,返回两个)
get_running_ports() {
local pid1
local pid2
local running_ports=""
if pid1=$(is_port_running $PORT1); then
running_ports="${PORT1}:${pid1}"
fi
if pid2=$(is_port_running $PORT2); then
if [ -n "$running_ports" ]; then
running_ports="${running_ports},${PORT2}:${pid2}"
else
running_ports="${PORT2}:${pid2}"
fi
fi
echo "$running_ports"
}
# === 原有函数(修改) ===
usage() {
echo "Usage: sh $0 [start|stop|restart|status]"
exit 1
}
# 检查是否有任何实例运行(兼容旧调用)
is_exist() {
if is_port_running $PORT1 >/dev/null || is_port_running $PORT2 >/dev/null; then
return 0
else
return 1
fi
}
# 启动日志(仅输出启动结果)
start_log() {
local port=$1
local jar_name=$(get_jar_name $port)
local new_pid=$(is_port_running $port)
if [ -n "$new_pid" ]; then
echo "${jar_name} 启动成功! pid=${new_pid} 端口=${port}"
else
echo "${jar_name} 启动失败!请检查后重试"
fi
}
# 启动方法(核心逻辑)
start() {
local running_info=$(get_running_ports)
local current_port=""
local current_pid=""
# 情况1:两个实例都在运行
if [[ "$running_info" == *"${PORT1}"* && "$running_info" == *"${PORT2}"* ]]; then
echo "检测到两个实例都在运行,将比较启动时间戳并杀掉较新的一个..."
# 解析两个端口和pid
IFS=',' read -ra entries <<< "$running_info"
declare -A port_pid
for entry in "${entries[@]}"; do
port=${entry%:*}
pid=${entry#*:}
port_pid[$port]=$pid
done
time1=$(get_start_time ${port_pid[$PORT1]})
time2=$(get_start_time ${port_pid[$PORT2]})
if [ $time1 -gt $time2 ]; then
kill_pid=${port_pid[$PORT1]}
kill_port=$PORT1
else
kill_pid=${port_pid[$PORT2]}
kill_port=$PORT2
fi
echo "杀掉较新实例:端口 ${kill_port} PID ${kill_pid}"
kill -9 $kill_pid
# 杀掉后,剩下的那个就是我们要保留的
if [ $kill_port -eq $PORT1 ]; then
current_port=$PORT2
current_pid=${port_pid[$PORT2]}
else
current_port=$PORT1
current_pid=${port_pid[$PORT1]}
fi
echo "保留实例:端口 ${current_port} PID ${current_pid}"
# 情况2:只有一个实例在运行
elif [ -n "$running_info" ]; then
# 解析当前运行的端口和pid
IFS=',' read -ra entries <<< "$running_info"
entry="${entries[0]}"
current_port=${entry%:*}
current_pid=${entry#*:}
echo "当前运行实例:端口 ${current_port} PID ${current_pid}"
# 情况3:无实例运行
else
echo "没有运行中的实例,将启动默认端口 ${PORT1}"
current_port=""
fi
# 决定要启动的新端口
if [ -z "$current_port" ]; then
new_port=$PORT1
else
if [ $current_port -eq $PORT1 ]; then
new_port=$PORT2
else
new_port=$PORT1
fi
fi
echo "准备启动新实例:端口 ${new_port}"
# 复制基础 jar 包为带端口的 jar(确保使用最新代码)
local base_jar="${APP_PATH}/${APP_BASE}.jar"
local target_jar="${APP_PATH}/$(get_jar_name $new_port)"
if [ ! -f "$base_jar" ]; then
echo "错误:基础 jar 包不存在 ${base_jar}"
exit 1
fi
cp "$base_jar" "$target_jar"
echo "已复制 ${base_jar} -> ${target_jar}"
# 启动新实例
nohup ${JAVA_HOME}/java -jar "$target_jar" \
--server.port=$new_port \
--spring.profiles.active=prod \
--spring.cloud.nacos.config.server-addr=127.0.0.1:8848 \
> /dev/null 2>&1 &
start_log $new_port
# 如果之前有旧实例在运行,等待新实例就绪后将其停止(滚动更新)
#if [ -n "$current_port" ]; then
# echo "等待 5 秒,确保新实例稳定..."
# sleep 5
# 再次确认新实例已启动
# if is_port_running $new_port >/dev/null; then
# echo "新实例已就绪,停止旧实例(端口 ${current_port} PID ${current_pid})"
# kill $current_pid
# echo "旧实例已停止"
# else
# echo "警告:新实例未能成功启动,旧实例未停止"
# fi
#fi
}
# 停止所有实例
stop() {
local stopped=0
for port in $PORT1 $PORT2; do
local pid=$(is_port_running $port)
if [ -n "$pid" ]; then
kill $pid
echo "已关闭 $(get_jar_name $port) pid=${pid}"
stopped=1
fi
done
if [ $stopped -eq 0 ]; then
echo "没有运行中的实例"
fi
}
# 输出运行状态
status() {
local running_info=$(get_running_ports)
if [ -z "$running_info" ]; then
echo "没有运行中的实例"
else
IFS=',' read -ra entries <<< "$running_info"
for entry in "${entries[@]}"; do
port=${entry%:*}
pid=${entry#*:}
start_time=$(get_start_time $pid)
start_str=$(date -d "@$start_time" "+%Y-%m-%d %H:%M:%S" 2>/dev/null)
echo "$(get_jar_name $port) 运行中 pid=${pid} 启动时间=${start_str}"
done
fi
}
# 重启:先停止所有,再启动默认端口
restart() {
stop
echo "准备重启..."
sleep 3
# 清空当前运行信息,启动新实例
start
}
# === 参数解析 ===
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)