今天遇到一个远程升级的需求,通过接口去触发终端服务的接口,重新拉取最新的jar包,并重启终端服务,这个终端服务是用java写的。 实现该需求,两个步骤,一个是需要一个shell脚本:拉取jar包、kill掉服务、启动服务;还有一个就是java中收到消息去调用shell脚本。

脚本

启动命令:

/root/dtest/upgrade.sh jar-name 端口 jar下载地址 jar存放路径
1 # !/bin/bash2 echo "start upgrade......"
3 ## 判断参数是否正确4 ########### jar包名称 ############5 APPLICATION_NAME=""
6 if [ ! $1 ]; then
7 echo "待执行的jar名称 IS NULL"
8 exit 1
9 else
10 APPLICATION_NAME=$1".jar"
11 fi
12 SERVER_PORT=""
13 if [ ! $2 ]; then
14 echo "端口 IS NULL"
15 exit 1
16 else
17 SERVER_PORT=$2
18 fi
19 ########### 软件包下载地址 ############20 FILE_URL=""
21 if [ ! $3 ]; then
22 echo "软件包下载地址 IS NULL"
23 exit 1
24 else
25 FILE_URL=$3
26 fi
27 BASE_PATH=""
28 if [ ! $4 ]; then
29 echo "软件包下载地址 IS NULL"
30 BASE_PATH="/usr/local/docker"
31 else
32 BASE_PATH=$4
33 fi
34 ## kill掉进程35 PROCESS=`ps -ef|grep $APPLICATION_NAME|grep -v grep|grep -v PPID|awk ‘{ print $2}‘`36 for i in$PROCESS37 do
38 echo "停止服务:kill the $APPLICATION_NAME process [ $i ]"
39 kill -9$i40 done
41 ##备份42 rm -rf $APPLICATION_NAME43 ## 下载应用服务包44 echo "download the application package"
45 echo "升级包下载命令 wget $FILE_URL -O"/root/dtest/"$APPLICATION_NAME"
46 wget $FILE_URL -O "/root/dtest/"$APPLICATION_NAME47 echo "升级包下载完成!!!"
48 ## 启动服务49 echo "开始启动 $1 服务"
50 chmod 777$APPLICATION_NAME51 nohup java -server -jar -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -Xms256m -Xmx512m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC $BASE_PATH"/"$APPLICATION_NAME --server.port=$SERVER_PORT 2>&1 &
52 echo "服务启动命令:java -server -jar -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -Xms256m -Xmx512m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC $BASE_PATH"/"$APPLICATION_NAME --server.port=$SERVER_PORT > /dev/null 2>&1 &"
53 for st in $(seq 1 20)54 do
55 # PID=$(netstat -nlp | grep :$EXECUTE_PORT | awk ‘{print $7}‘ | awk -F"/" ‘{ print $1 }‘);56 PID=$(netstat -nlp | grep :$SERVER_PORT | awk ‘{print $7}‘ |sed ‘s/\([0-9]*\).*/\1/g‘);57 if [ $st -eq 20 ] && [ -z "$PID" ]; then
58 echo "服务启动失败"### $PID 为空59 break60 fi
61
62 if [ -z "$PID" ]; then
63 sleep 2
64 echo $st"服务启动中...."### $PID 为空65 else
66 echo "服务名称:$APPLICATION_NAME ,端口号为:$SERVER_PORT ,进程号为:$PID 启动成功 , 耗时:$[$[st-1]*3] seconds!!!"
67 break68 fi
69 done

Java程序

java调用shell脚本有多种方式,简单粗暴的方式是:Runtime.getRuntime().exec()

但现实给我上了一课,当kill掉自己服务后,后面的脚本也停止执行了,原因处在,当服务执行自身重启的命令时,父进程关闭导致管道连接中断,将导致子进程也崩溃,从而无法完成后续的启动。

解决方式,

设置子进程IO输出重定向到指定文件

设置属性子进程的I/O源或目标将与当前进程的相同,两者相互独立

上代码:(源码下载)

1 packagecom.dzh.boot.demo.controller;2
3 importorg.springframework.beans.factory.annotation.Value;4 importorg.springframework.web.bind.annotation.RequestMapping;5 importorg.springframework.web.bind.annotation.RestController;6
7 importjava.io.File;8 importjava.io.IOException;9
10 /**
11 * 升级当前服务 --- 拉取最新jar,杀掉当前服务,启动最新的jar12 * @date 2021.4.913 */
14 @RestController15 public classUpgradeController {16
17 //脚本的地址
18 @Value("${my.test.scriptPath}")19 privateString scriptPath;20
21 /**
22 * jar包的名称 去掉.jar23 */
24 @Value("${my.test.name}")25 privateString applicationName;26
27 /**
28 * 服务启动的端口29 */
30 @Value("${server.port}")31 privateString port;32
33 /**
34 * 最新jar包的下载地址35 */
36 @Value("${my.test.fileUrl}")37 privateString fileUrl;38
39 /**
40 * jar包放置的路径41 */
42 @Value("${my.test.basePath}")43 privateString basePath;44
45 /**
46 * 触发升级47 *@return
48 *@throwsException49 */
50 @RequestMapping("run")51 private String run() throwsException {52 ProcessBuilder sh = new ProcessBuilder("sh", scriptPath, applicationName, port, fileUrl, basePath);53 asynExeLocalComand(null, sh);54 return "成功";55 }56
57 /**
58 * 用来检查服务是否正常59 *@return
60 *@throwsIOException61 */
62 @RequestMapping("getParam")63 private String getParam() throwsIOException {64 return scriptPath + " " + applicationName + " " + fileUrl + " " + basePath + " " +port;65 }66
67
68
69 public static void asynExeLocalComand(File file, ProcessBuilder pb) throwsIOException {70 //不使用Runtime.getRuntime().exec(command)的方式,因为无法设置以下特性71 //Java执行本地命令是启用一个子进程处理,默认情况下子进程与父进程I/O通过管道相连(默认ProcessBuilder.Redirect.PIPE)72 //当服务执行自身重启的命令时,父进程关闭导致管道连接中断,将导致子进程也崩溃,从而无法完成后续的启动73 //解决方式,(1)设置子进程IO输出重定向到指定文件;(2)设置属性子进程的I/O源或目标将与当前进程的相同,两者相互独立
74 if (file == null || !file.exists()) {75 //设置属性子进程的I/O源或目标将与当前进程的相同,两者相互独立
76 pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);77 pb.redirectError(ProcessBuilder.Redirect.INHERIT);78 pb.redirectInput(ProcessBuilder.Redirect.INHERIT);79 } else{80 //设置子进程IO输出重定向到指定文件81 //错误输出与标准输出,输出到一块
82 pb.redirectErrorStream(true);83 //设置输出日志
84 pb.redirectOutput(ProcessBuilder.Redirect.appendTo(file));85 }86 //执行命令进程
87 pb.start();88 }89
90 }