1、背景:
接触的一个建设项目,微服务使用的比较多,在更新部署程序的时候总是大量时间,且根据日志排查困难较大,虽有脚本可以勉强执行,但侵入较大,换个环境改动地方多,深感痛击。于是乎,花费一周时间结合网上部分大佬文章对关键代码根据项目实际情况梳理改造,在此表示感谢。
2、思路:
- 启动配置参数文件与服务启停的脚本文件分开,降低耦合;
- 配置文件尽量高可用,配置参数变量化。
- 启停脚本文件高可用,引用配置文件实现高可用,几乎不用修改。
3、难点:
- 多服务的配置文件,参数拼接难度较大。
- 对特殊字符的处理,降低执行脚本的出错率。
- 服务功能扩展,增加监控等使用功能。
4、实现的功能:
- 单服务、批量服务的start,stop,restart管理;
- 基于1,实现服务状态及配置的查看;
- 日志的展示功能及各java服务的内存,cpu监控。
5、功能的落地实现(脱敏脱密处理):
1、目录结构说明

delpoy:服务目录,部署的微服务,及Log文件的存储

script:脚本目录,放置配置文件及其他组件

2、配置文件(service.conf)说明
1、前提说明
# SpringBoot服务配置文件
# 格式: 服务名称|JAR文件路径|端口号|Profile环境|JVM参数
# 示例配置,请根据实际情况修改
# springboot-service1|/app/deploy/spring-boot.jar|8080|dev_profile|-Xmx512m
# 注意事项:
# 1. 每行一个服务配置
# 2. 使用 | 分隔各个字段
# 3. JAR文件路径必须是绝对路径
# 4. 端口号用于健康检查
# 5. Profile环境可以为空,默认使用default
# 6. JVM参数可以为空,使用默认配置
# 7. 以#开头的行为注释行
# 8.# [service]遍历服务开始,放在最后,切勿更改及删除
2、大体分为以下几项:
- 启动参数的基本配置
- 参数的整合
- 其他参数配置
- 各微服务个性化配置
- 服务的放置目录
- 参数拼接整合
- 拼接各服务组成执行命令
举例:





3、服务脚本(springboot-service-manager.sh)说明:
1、大体功能
- 首页显示信息
- 依赖查询
- 主菜单
- 加载服务配置
- 服务管理
- start、stop、restart管理
- 系统资源信息
- 各服务详细信息展示
- 日志查看
- 服务状态检查
2、部分截图:


脚本文件不不过多展示,文末获取
3、效果展示:
首页展示
单个服务展示


启停展示(优雅停止,超时则暴力停止 kill -9)



批量操作

通过nacos控制台也可以看到服务状态信息

6、整体脚本文件
services.conf(根据实际项目修改调整)
# 注意事项:
# 1. 每行一个服务配置
# 2. 使用 | 分隔各个字段
# 3. JAR文件路径必须是绝对路径
# 4. 端口号用于健康检查
# 5. Profile环境可以为空,默认使用default
# 6. JVM参数可以为空,使用默认配置
# 7. 以#开头的行为注释行
# 8.# [service]遍历服务开始,放在最后,切勿更改及删除
#基本信息
SCRIPT_DIR=/data/script
DEPLOY_DIR=/data/deploy
LOG_DIR=/data/deploy/logs
GCLOG_DIR=/data/deploy/logs/gclog
#nacos_config地址
NACOS_ADDRESS=-Dspring.cloud.nacos.confi
NACOS_OFFSET=-Dnacos.server.grpc.port.offset=1
...................
LOG4J2_PATH=-Dlogging.config=$SCRIPT_DIR/log
#公用配置
#NACOS_SHARE_COMMON=-Dspring.cloud.nacos..........
#nacos配置安全认证(暂不需要)
#TONG_WEB配置
............
#FASTJSON配置
FASTJSON_SAFEMODE=-Dfastjson.parser.safeMode=true
#后台启动时是否删除当前日志文件
DELETE_APPLOG=1
#内存溢出问题排查
DumpOnOutOfMemory=-XX:-UseGCOverheadLimit -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=...........
#整合启动参数
START_INIT=$NACOS_ADDRESS $NACOS..........
#整合其他启动参数
START_OTHER=$DumpOnOutOfMemory $........
#应用目录
.......
#各应用参数
*****_ARGS=-DSW_AGENT_NAME=******* -Xms****** -Dserver.port=*****
........
#各应用ALL参数
********_ALL=$START_INIT $START_OTHER $******_ARGS
# [service]
*******|$****|***0|$HJ_TYPE|$******_ALL
...........
#interface-service2|/data/sp/kjh.1.0.0.jar|8325||-Dfastjson.parser.safeMode=true -Dserver.port=8384 --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
springboot-service-manager.sh(几乎拿来即用)
#!/bin/bash
# SpringBoot服务管理脚本 - 可视化版本
# 支持多个服务的启动、停止、重启、状态查看等功能
# 配置目录
CONFIG_FILE="/data/script/services.conf"
LOG_DIR="/data/deploy/logs"
# 创建必要目录
mkdir -p "$LOG_DIR/gclog"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
# 加载服务配置
load_services() {
if [[ ! -f "$CONFIG_FILE" ]]; then
echo -e "${RED}配置文件不存在: $CONFIG_FILE${NC}"
echo "请先创建配置文件,参考 services.conf.example"
exit 1
fi
# 定义数组
SERVICE_NAMES=() # 服务名称数组
SERVICE_PATHS=() # 服务路径数组
SERVICE_PORTS=() # 服务端口数组
SERVICE_PROFILES=() # 服务配置文件数组
SERVICE_JVM_OPTS=() # JVM选项数组
# 临时文件用于存储解析后的配置
TEMP_CONFIG=$(mktemp)
# 预处理配置文件:解析变量定义
while IFS= read -r line; do
# 跳过注释行和空行
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
# 处理变量定义 (KEY=VALUE 格式)
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$ ]]; then
var_name="${BASH_REMATCH[1]}"
var_value="${BASH_REMATCH[2]}"
# 处理export前缀
if [[ "$var_name" == "export" ]]; then
if [[ "$var_value" =~ ^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$ ]]; then
var_name="${BASH_REMATCH[1]}"
var_value="${BASH_REMATCH[2]}"
export "$var_name"="$var_value"
fi
else
# 普通变量定义
declare -g "$var_name"="$var_value"
fi
# echo "已定义变量: $var_name=${!var_name}"
fi
done < "$CONFIG_FILE"
# 第二遍处理:解析服务配置并替换变量
while IFS= read -r line; do
# 跳过注释行和空行
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
# 跳过变量定义行
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)= ]]; then
continue
fi
# 处理服务配置行 (格式: 服务名称|JAR文件路径|端口号|Profile环境|JVM参数)
if [[ "$line" =~ ^([^|]+)\|([^|]+)\|([^|]+)\|([^|]*)\|(.*)$ ]]; then
service_name="${BASH_REMATCH[1]}"
jar_path="${BASH_REMATCH[2]}"
port="${BASH_REMATCH[3]}"
profile="${BASH_REMATCH[4]}"
jvm_args="${BASH_REMATCH[5]}"
# 变量替换函数
replace_vars() {
local str="$1"
# 替换所有 $VAR 或${VAR} 格式的变量
while [[ "$str" =~ \$([a-zA-Z_][a-zA-Z0-9_]*)|\$\{([a-zA-Z_][a-zA-Z0-9_]*)\} ]]; do
local var_name="${BASH_REMATCH[1]:-${BASH_REMATCH[2]}}"
local var_value="${!var_name}"
str="${str//\$$var_name/$var_value}"
str="${str//\$\{$var_name\}/$var_value}"
done
echo "$str"
}
# 替换所有变量
jar_path=$(replace_vars "$jar_path")
profile=$(replace_vars "$profile")
jvm_args=$(replace_vars "$jvm_args")
# 保存解析后的配置
echo "$service_name|$jar_path|$port|$profile|$jvm_args" >> "$TEMP_CONFIG"
SERVICE_NAMES+=("$service_name")
SERVICE_PATHS+=("$jar_path")
SERVICE_PORTS+=("$port")
SERVICE_PROFILES+=("$profile")
SERVICE_JVM_OPTS+=("$jvm_args")
fi
done < "$CONFIG_FILE"
rm -rf $TEMP_CONFIG
}
# 获取服务PID
get_service_pid() {
local service_name=$1
local port=$2
# 先通过端口查找
# if [[ -n "$port" ]]; then
# pid=$(lsof -ti:$port 2>/dev/null)
# if [[ -n "$pid" ]]; then
# echo $pid
# return
# fi
# fi
# 通过jar文件名查找
pid=$(ps aux | grep java | grep -v grep | grep "$service_name" | awk '{print $2}')
echo $pid
}
# 检查服务状态
check_service_status() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local port=${SERVICE_PORTS[$index]}
local pid=$(get_service_pid "$service_name" "$port")
if [[ -n "$pid" ]]; then
# 检查端口是否可访问
if [[ -n "$port" ]] && nc -z localhost $port 2>/dev/null; then
echo -e "${GREEN}运行中${NC} (PID: $pid, Port: $port)"
else
echo -e "${YELLOW}启动中${NC} (PID: $pid)"
fi
return 0
else
echo -e "${RED}已停止${NC}"
return 1
fi
}
# 启动服务
start_service() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local jar_path=${SERVICE_PATHS[$index]}
local port=${SERVICE_PORTS[$index]}
local profile=${SERVICE_PROFILES[$index]}
local jvm_opts=${SERVICE_JVM_OPTS[$index]}
echo -e "${BLUE}正在启动服务: $service_name${NC}"
# 检查jar文件是否存在
if [[ ! -f "$jar_path" ]]; then
echo -e "${RED}错误: JAR文件不存在 - $jar_path${NC}"
return 1
fi
# 检查服务是否已经运行
local pid=$(get_service_pid "$service_name" "$port")
if [[ -n "$pid" ]]; then
echo -e "${YELLOW}服务已经在运行中 (PID: $pid)${NC}"
return 0
fi
# 构建启动命令
local cmd="java"
[[ -n "$jvm_opts" ]] && cmd="$cmd $jvm_opts"
[[ -n "$profile" ]] && cmd="$cmd -Dspring.profiles.active=$profile"
cmd="$cmd -jar $jar_path"
# 日志处理
local log_file="$LOG_DIR/${service_name}.log"
if [ -e $log_file -a "$DELETE_APPLOG" == "1" ];then
> $log_file
echo "清空日志文件${service_name}.log----------------------"
fi
# 启动服务
nohup $cmd >> "$log_file" 2>&1 &
local new_pid=$!
echo "启动命令: $cmd" >> "$log_file"
echo "启动时间: $(date)" >> "$log_file"
echo "进程PID: $new_pid" >> "$log_file"
echo "----------------------------------------" >> "$log_file"
# 等待服务启动
echo -n "等待服务启动..."
sleep 2
if [[ -n "$port" ]] && nc -z localhost $port 2>/dev/null; then
echo
echo -e "${GREEN}服务启动成功!${NC} (PID: $new_pid, Port: $port)"
return 0
else
echo
echo -e "${YELLOW}服务已启动!${NC} (PID: $new_pid)"
echo "请查看日志文件: $log_file"
return 0
fi
}
# 停止服务
stop_service() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local port=${SERVICE_PORTS[$index]}
echo -e "${BLUE}正在停止服务: $service_name${NC}"
local pid=$(get_service_pid "$service_name" "$port")
if [[ -z "$pid" ]]; then
echo -e "${YELLOW}服务未运行${NC}"
return 0
fi
# 优雅停止
echo "发送TERM信号..."
kill -TERM $pid
# 等待服务停止
echo -n "等待服务停止..."
sleep 5
if ! kill -0 $pid 2>/dev/null; then
echo
echo -e "${GREEN}服务已停止${NC}"
return 0
else
# 强制停止
echo
echo "优雅停止超时,强制停止..."
kill -KILL $pid 2>/dev/null
fi
sleep 2
if ! kill -0 $pid 2>/dev/null; then
echo -e "${GREEN}服务已强制停止${NC}"
return 0
else
echo -e "${RED}服务停止失败${NC}"
return 1
fi
}
# 重启服务
restart_service() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
echo -e "${BLUE}正在重启服务: $service_name${NC}"
stop_service $index
sleep 1
start_service $index
}
# 查看服务日志
view_service_log() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local log_file="$LOG_DIR/${service_name}.log"
if [[ ! -f "$log_file" ]]; then
echo -e "${RED}日志文件不存在: $log_file${NC}"
return 1
fi
echo -e "${BLUE}查看服务日志: $service_name${NC}"
echo "日志文件: $log_file"
echo "----------------------------------------"
# 选择查看方式
echo "请选择查看方式:"
echo "1) 查看最后50行"
echo "2) 查看最后100行"
echo "3) 实时跟踪日志"
echo "4) 查看全部日志"
echo "0) 返回主菜单"
read -p "请输入选择 [1-4]: " log_choice
case $log_choice in
1) tail -50 "$log_file" ;;
2) tail -100 "$log_file" ;;
3) echo "按 Ctrl+C 退出实时跟踪"
sleep 2
tail -f "$log_file" ;;
4) less "$log_file" ;;
0) return ;;
*) echo -e "${RED}无效选择${NC}" ;;
esac
}
# 显示服务详细信息
show_service_detail() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local jar_path=${SERVICE_PATHS[$index]}
local port=${SERVICE_PORTS[$index]}
local profile=${SERVICE_PROFILES[$index]}
local jvm_opts=${SERVICE_JVM_OPTS[$index]}
clear
echo -e "${CYAN}==================== 服务详细信息 ====================${NC}"
echo -e "${WHITE}服务名称:${NC} $service_name"
echo -e "${WHITE}JAR路径:${NC} $jar_path"
echo -e "${WHITE}端口号:${NC} ${port:-未配置}"
echo -e "${WHITE}环境配置:${NC} ${profile:-默认}"
echo -e "${WHITE}JVM参数:${NC} ${jvm_opts:-默认}"
local pid=$(get_service_pid "$service_name" "$port")
echo -e "${WHITE}运行状态:${NC} $(check_service_status $index)"
if [[ -n "$pid" ]]; then
echo -e "${WHITE}进程PID:${NC} $pid"
echo -e "${WHITE}内存使用:${NC} $(ps -p $pid -o rss= | awk '{printf "%.1f MB", $1/1024}')"
echo -e "${WHITE}CPU使用:${NC} $(ps -p $pid -o %cpu= | awk '{print $1"%"}')"
echo -e "${WHITE}启动时间:${NC} $(ps -p $pid -o lstart= | awk '{print $1" "$2" "$3" "$4}')"
fi
local log_file="$LOG_DIR/${service_name}.log"
if [[ -f "$log_file" ]]; then
echo -e "${WHITE}日志文件:${NC} $log_file"
echo -e "${WHITE}日志大小:${NC} $(du -h "$log_file" | awk '{print $1}')"
fi
echo -e "${CYAN}======================================================${NC}"
echo
read -p "按回车键返回主菜单..."
}
# 显示系统资源使用情况
show_system_info() {
clear
echo -e "${CYAN}==================== 系统资源信息 ====================${NC}"
# CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
echo -e "${WHITE}CPU使用率:${NC} ${cpu_usage}%"
# 内存使用情况
memory_info=$(free -h | grep Mem)
total_mem=$(echo $memory_info | awk '{print $2}')
used_mem=$(echo $memory_info | awk '{print $3}')
echo -e "${WHITE}内存使用:${NC} $used_mem / $total_mem"
# 磁盘使用情况
echo -e "${WHITE}磁盘使用:${NC}"
df -h | grep -E '^/dev/' | awk '{printf " %s: %s / %s (%s)\n", $1, $3, $2, $5}'
# Java进程信息
echo -e "${WHITE}Java进程:${NC}"
ps aux | grep java | grep -v grep | while read line; do
pid=$(echo $line | awk '{print $2}')
mem=$(echo $line | awk '{print $4}')
cmd=$(echo $line | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}')
echo " PID: $pid, MEM: ${mem}%, CMD: ${cmd:0:50}..."
done
echo -e "${CYAN}======================================================${NC}"
echo
read -p "按回车键返回主菜单..."
}
# 批量操作菜单
batch_operations_menu() {
while true; do
clear
echo -e "${PURPLE}==================== 批量操作菜单 ====================${NC}"
echo "1) 启动所有服务"
echo "2) 停止所有服务"
echo "3) 重启所有服务"
echo "4) 查看所有服务状态"
echo "0) 返回主菜单"
echo -e "${PURPLE}======================================================${NC}"
read -p "请输入选择 [0-4]: " batch_choice
case $batch_choice in
1)
echo -e "${BLUE}正在启动所有服务...${NC}"
for ((i=0; i<${#SERVICE_NAMES[@]}; i++)); do
start_service $i
echo
done
read -p "按回车键继续..."
;;
2)
echo -e "${BLUE}正在停止所有服务...${NC}"
for ((i=0; i<${#SERVICE_NAMES[@]}; i++)); do
stop_service $i
echo
done
read -p "按回车键继续..."
;;
3)
echo -e "${BLUE}正在重启所有服务...${NC}"
for ((i=0; i<${#SERVICE_NAMES[@]}; i++)); do
restart_service $i
echo
done
read -p "按回车键继续..."
;;
4)
clear
echo -e "${CYAN}==================== 所有服务状态 ====================${NC}"
printf "%-20s %-10s %-15s\n" "服务名称" "端口" "状态"
echo "------------------------------------------------------"
for ((i=0; i<${#SERVICE_NAMES[@]}; i++)); do
status=$(check_service_status $i)
printf "%-15s %-10s %s\n" "${SERVICE_NAMES[$i]}" "${SERVICE_PORTS[$i]}" "$status"
done
echo -e "${CYAN}======================================================${NC}"
read -p "按回车键继续..."
;;
0)
break
;;
*)
echo -e "${RED}无效选择,请重新输入${NC}"
sleep 1
;;
esac
done
}
# 服务管理菜单
service_management_menu() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
while true; do
clear
echo -e "${CYAN}==================== 服务管理: $service_name ====================${NC}"
echo -e "当前状态: $(check_service_status $index)"
echo
echo "1) 启动服务"
echo "2) 停止服务"
echo "3) 重启服务"
echo "4) 查看日志"
echo "5) 服务详情"
echo "0) 返回主菜单"
echo -e "${CYAN}================================================================${NC}"
read -p "请输入选择 [0-5]: " service_choice
case $service_choice in
1)
start_service $index
read -p "按回车键继续..."
;;
2)
stop_service $index
read -p "按回车键继续..."
;;
3)
restart_service $index
read -p "按回车键继续..."
;;
4)
view_service_log $index
;;
5)
show_service_detail $index
;;
0)
break
;;
*)
echo -e "${RED}无效选择,请重新输入${NC}"
sleep 1
;;
esac
done
}
# 主菜单
main_menu() {
while true; do
clear
echo -e "${GREEN}#################### SpringBoot服务管理器 ####################${NC}"
echo -e "${WHITE}当前时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo -e "${WHITE}配置文件: $CONFIG_FILE${NC}"
echo -e "${WHITE}日志目录: $LOG_DIR${NC}"
echo
# 显示服务列表和状态
if [[ ${#SERVICE_NAMES[@]} -eq 0 ]]; then
echo -e "${RED}未找到任何服务配置${NC}"
else
echo -e "${CYAN}================== 服务列表 ==================${NC}"
printf "%-3s %-20s %-10s %-15s\n" "序号" "服务名称" "端口" "状态"
echo "-----------------------------------------------"
for ((i=0; i<${#SERVICE_NAMES[@]}; i++)); do
status=$(check_service_status $i)
printf "%-3s %-20s %-10s %s\n" "$((i+1))" "${SERVICE_NAMES[$i]}" "${SERVICE_PORTS[$i]}" "$status"
done
echo -e "${CYAN}===============================================${NC}"
fi
echo
echo "操作选项:"
echo "1-${#SERVICE_NAMES[@]}) 管理对应服务"
echo "b) 批量操作"
echo "s) 系统信息"
echo "r) 重新加载配置"
echo "q) 退出程序"
echo -e "${GREEN}#########################################################${NC}"
read -p "请输入选择: " main_choice
case $main_choice in
[1-9]|[1-9][0-9])
index=$((main_choice-1))
if [[ $index -ge 0 && $index -lt ${#SERVICE_NAMES[@]} ]]; then
service_management_menu $index
else
echo -e "${RED}无效的服务序号${NC}"
sleep 1
fi
;;
b|B)
batch_operations_menu
;;
s|S)
show_system_info
;;
r|R)
echo -e "${BLUE}重新加载配置文件...${NC}"
load_services
echo -e "${GREEN}配置加载完成${NC}"
sleep 1
;;
q|Q)
echo -e "${GREEN}感谢使用SpringBoot服务管理器!${NC}"
exit 0
;;
*)
echo -e "${RED}无效选择,请重新输入${NC}"
sleep 1
;;
esac
done
}
# 检查依赖命令
check_dependencies() {
local missing_deps=()
command -v java >/dev/null 2>&1 || missing_deps+=("java")
command -v lsof >/dev/null 2>&1 || missing_deps+=("lsof")
command -v nc >/dev/null 2>&1 || missing_deps+=("netcat")
if [[ ${#missing_deps[@]} -ne 0 ]]; then
echo -e "${RED}错误: 缺少必要的命令工具${NC}"
echo "请安装以下工具: ${missing_deps[*]}"
echo
echo "Ubuntu/Debian: sudo apt-get install openjdk-8-jdk lsof netcat"
echo "CentOS/RHEL: sudo yum install java-1.8.0-openjdk lsof nc"
exit 1
fi
}
# 主程序入口
main() {
# 检查依赖
check_dependencies
# 加载服务配置
load_services
# 显示欢迎信息
clear
echo -e "${GREEN}"
echo "########################################################"
echo "# #"
echo "# SpringBoot服务管理器 v1.0 #"
echo "# #"
echo "# 支持多服务管理、日志查看、状态监控 #"
echo "# #"
echo "########################################################"
echo -e "${NC}"
sleep 1
# 启动主菜单
main_menu
}
# 脚本执行入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi

794

被折叠的 条评论
为什么被折叠?



