Spring Boot 實現看門狗功能 (調用 Shell 腳本)

需要實現看門狗功能,定時檢測另外一個程序是否在運行,使用 crontab 僅可以實現檢測程序是否正在運行,無法做到擴展,如:手動重啓、程序升級(如果只需要實現自動升級功能可以使用 inotify)等功能;最後決定使用 Spring Boot 調用 Shell 腳本來實現

一、腳本

1.1 啓動腳本

#!/bin/bash

ps -ef | grep "demo-app-0.0.1-SNAPSHOT.jar" | grep -v "grep"

if [ "$?" -eq 0 ]
then

# sleep
echo $(date "+%Y-%m-%d %H:%M:%S") "process already started!"

else

nohup java -jar -server /project/watchdog/demo-app-0.0.1-SNAPSHOT.jar &
echo $(date "+%Y-%m-%d %H:%M:%S") "process has been started!"

fi

1.2 重啓腳本

#!/bin/bash

pid=`ps -ef | grep "demo-app-0.0.1-SNAPSHOT.jar" | grep -v "grep" | awk '{print $2}'`

for id in $pid
do
    kill -9 $id
    echo "killed $id"
done

nohup java -jar -server /project/watchdog/demo-app-0.0.1-SNAPSHOT.jar &
echo $(date "+%Y-%m-%d %H:%M:%S") "process has been restarted!"

二、功能實現

將腳本放置在程序的資源目錄下,每次程序啓動時將腳本讀取到指定位置,然後再通過定時任務執行腳本

配置內容:

shell:
  directory: /project/watchdog
  startupFileName: startup.sh
  restartFileName: restart.sh
@Configuration
@ConfigurationProperties(prefix = "shell")
public class ShellProperties {

    private String directory;
    private String startupFileName;
    private String restartFileName;

    /* getter & setter */

    public String getFullName(String fileName) {
        return directory + File.separator + fileName;
    }
}

2.1 啓動時將腳本讀取到指定位置

@Component
public class InitRunner implements CommandLineRunner {

    @Autowired
    private ShellProperties shellProperties;

    @Autowired
    ResourceLoader resourceLoader;

    @Override
    public void run(String... args) throws Exception {
        generateFile(shellProperties.getStartupFileName());
        generateFile(shellProperties.getRestartFileName());
    }

    private void generateFile(String fileName) throws IOException {

        String fileFullName = shellProperties.getFullName(fileName);
        File file = new File(fileFullName);
        if(file.exists()) {
            return;
        }

        // 如果文件已存在,FileWriter 會先刪除再新建
        FileWriter fileWriter = new FileWriter(fileFullName);

        Resource resource = resourceLoader.getResource("classpath:" + fileName);
        InputStream inputStream = resource.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        String data;
        while ((data = bufferedReader.readLine()) != null) {
            fileWriter.write(data + "\n");
        }

        bufferedReader.close();
        inputStreamReader.close();
        inputStream.close();

        fileWriter.close();

        // 設置權限,否則會報 Permission denied
        file.setReadable(true);
        file.setWritable(true);
        file.setExecutable(true);
    }
}

2.2 定時任務定時任務執行腳本

@Component
public class ShellTask {

    private static final Logger logger = LoggerFactory.getLogger(ShellTask.class);

    @Autowired
    private ShellProperties shellProperties;

    @Scheduled(cron = "0/10 * * * * ? ")
    public void start() throws IOException {
        executeShell(shellProperties.getStartupFileName());
    }

    private void executeShell(String fileName) throws IOException {

        String fileFullName = shellProperties.getFullName(fileName);
        File file = new File(fileFullName);
        if(!file.exists()) {
            logger.error("file {} not existed!", fileFullName);
            return;
        }

        ProcessBuilder processBuilder = new ProcessBuilder(fileFullName);
        processBuilder.directory(new File(shellProperties.getDirectory()));

        Process process = processBuilder.start();

//        String input;
//        BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
//        BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//        while ((input = stdInput.readLine()) != null) {
//            logger.info(input);
//        }
//        while ((input = stdError.readLine()) != null) {
//            logger.error(input);
//        }

        int runningStatus = 0;
        try {
            runningStatus = process.waitFor();
        } catch (InterruptedException e) {
            logger.error("shell", e);
        }

        if(runningStatus != 0) {
            logger.error("failed.");
        }else {
            logger.info("success.");
        }
    }
}

2.3 擴展

  1. 本例只實現了定時檢測程序是否運行,如果沒有運行則啓動程序;如有需要可以添加接口,調用接口重啓程序;或者添加定時任務定時檢測程序是否有更新,如果有更新則下載新的 jar 包然後重啓程序
  2. 看門狗程序自己可以使用 crontab 定時檢測是否正在運行,模仿上面的啓動腳本編寫看門狗的啓動腳本,然後添加定時任務:
crontab -e
*/10 * * * * /project/watchdog/watchdog.sh
sudo systemctl reload crond.service

完整代碼:GitHub

參考:java去調用並執行shell腳本以及問題總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章