今天遇到一個遠程升級的需求,通過接口去觸發終端服務的接口,重新拉取最新的jar包,並重啟終端服務,這個終端服務是用java寫的。 實現該需求,兩個步驟,一個是需要一個shell腳本:拉取jar包、kill掉服務、啟動服務;還有一個就是java中收到消息去調用shell腳本。
腳本
啟動命令:
/root/dtest/upgrade.sh jar-name 端口 jar下載地址 jar存放路徑
1 # !/bin/bash 2 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 $PROCESS 37 do 38 echo "停止服務:kill the $APPLICATION_NAME process [ $i ]" 39 kill -9 $i 40 done 41 ##備份 42 rm -rf $APPLICATION_NAME 43 ## 下載應用服務包 44 echo "download the application package" 45 echo "升級包下載命令 wget $FILE_URL -O "/root/dtest/"$APPLICATION_NAME" 46 wget $FILE_URL -O "/root/dtest/"$APPLICATION_NAME 47 echo "升級包下載完成!!!" 48 ## 啟動服務 49 echo "開始啟動 $1 服務" 50 chmod 777 $APPLICATION_NAME 51 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 break 60 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 break 68 fi 69 done
Java程序
java調用shell腳本有多種方式,簡單粗暴的方式是:Runtime.getRuntime().exec()
但現實給我上了一課,當kill掉自己服務后,后面的腳本也停止執行了,原因處在,當服務執行自身重啟的命令時,父進程關閉導致管道連接中斷,將導致子進程也崩潰,從而無法完成后續的啟動。
解決方式,
- 設置子進程IO輸出重定向到指定文件
- 設置屬性子進程的I/O源或目標將與當前進程的相同,兩者相互獨立
上代碼:(源碼下載)
1 package com.dzh.boot.demo.controller; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.bind.annotation.RestController; 6 7 import java.io.File; 8 import java.io.IOException; 9 10 /** 11 * 升級當前服務 --- 拉取最新jar,殺掉當前服務,啟動最新的jar 12 * @date 2021.4.9 13 */ 14 @RestController 15 public class UpgradeController { 16 17 //腳本的地址 18 @Value("${my.test.scriptPath}") 19 private String scriptPath; 20 21 /** 22 * jar包的名稱 去掉.jar 23 */ 24 @Value("${my.test.name}") 25 private String applicationName; 26 27 /** 28 * 服務啟動的端口 29 */ 30 @Value("${server.port}") 31 private String port; 32 33 /** 34 * 最新jar包的下載地址 35 */ 36 @Value("${my.test.fileUrl}") 37 private String fileUrl; 38 39 /** 40 * jar包放置的路徑 41 */ 42 @Value("${my.test.basePath}") 43 private String basePath; 44 45 /** 46 * 觸發升級 47 * @return 48 * @throws Exception 49 */ 50 @RequestMapping("run") 51 private String run() throws Exception { 52 ProcessBuilder sh = new ProcessBuilder("sh", scriptPath, applicationName, port, fileUrl, basePath); 53 asynExeLocalComand(null, sh); 54 return "成功"; 55 } 56 57 /** 58 * 用來檢查服務是否正常 59 * @return 60 * @throws IOException 61 */ 62 @RequestMapping("getParam") 63 private String getParam() throws IOException { 64 return scriptPath + " " + applicationName + " " + fileUrl + " " + basePath + " " + port; 65 } 66 67 68 69 public static void asynExeLocalComand(File file, ProcessBuilder pb) throws IOException { 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 }