首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實現 Tomcat 存活監控與自動重啟:HTTP 健康檢查 + 進程級守護

     編輯:Java 相關知識

生產環境 Tomcat 偶發假死(埠口無響應但進程存在)或崩潰退出。本文提供一個 **純 Java 守護進程**,結合 **HTTP 健康檢查**與 **進程掃描**,實現「檢測到異常 → 優雅停止 → 強制殺死 → 重新啟動」的完整自愈閉環,附完整代碼、部署建議與系統級替代方案對比。

核心邏輯流程圖

定時任務 (每 30 秒)
    ↓
HTTP GET /health (timeout 8s)
    ├─ 成功 (200) → 正常,下一輪
    └─ 失敗/超時
        ↓
    掃描進程列表 (ps -aux) 確認 Tomcat 進程是否存在
        ├─ 存在 → 僵屍/假死 → 執行 shutdown.sh → 等待 → 執行 startup.sh
        └─ 不存在 → 已崩潰 → 直接執行 startup.sh
    ↓
記錄日誌、告警通知

完整守護類:TomcatWatchdog


import java.io.*;
import java.net.*;
import java.util.Date;

public class TomcatWatchdog {
    private static final String HEALTH_URL = "https://localhost:8080/health"; // 需在 Tomcat 部署健康檢查端點
    private static final String TOMCAT_HOME = "/opt/tomcat"; // 實際安裝路徑
    private static final String SHUTDOWN_CMD = TOMCAT_HOME + "/bin/shutdown.sh";
    private static final String STARTUP_CMD = TOMCAT_HOME + "/bin/startup.sh";
    private static final String PROCESS_GREP = "org.apache.catalina.startup.Bootstrap"; // 進程特徵
    private static final int CHECK_INTERVAL_MS = 30_000;

    public static void main(String[] args) {
        System.out.println("[" + new Date() + "] Tomcat Watchdog 啟動");
        while (true) {
            try {
                checkAndHeal();
            } catch (Exception e) {
                System.err.println("[" + new Date() + "] 守護異常: " + e.getMessage());
                e.printStackTrace();
            }
            sleep(CHECK_INTERVAL_MS);
        }
    }

    private static void checkAndHeal() throws IOException, InterruptedException {
        boolean httpOk = httpHealthCheck();
        boolean processAlive = isTomcatProcessAlive();

        if (httpOk) {
            System.out.println("[" + new Date() + "] HTTP 健康檢查通過");
            return;
        }

        System.out.println("[" + new Date() + "] HTTP 健康檢查失敗, 進程存活: " + processAlive);

        if (processAlive) {
            System.out.println("[" + new Date() + "] 偵測到假死/僵屍進程, 執行優雅關閉...");
            executeScript(SHUTDOWN_CMD, "Stopping");
            sleep(10_000); // 等待優雅關閉
            if (isTomcatProcessAlive()) {
                System.out.println("[" + new Date() + "] 優雅關閉失敗, 強制殺死進程...");
                killProcess(PROCESS_GREP);
                sleep(3_000);
            }
        } else {
            System.out.println("[" + new Date() + "] 進程已不存在, 直接啟動");
        }

        System.out.println("[" + new Date() + "] 執行啟動腳本...");
        executeScript(STARTUP_CMD, "Starting");
        sleep(15_000); // 等待啟動完成
        System.out.println("[" + new Date() + "] 重啟流程結束");
    }

    private static boolean httpHealthCheck() {
        try {
            URL url = new URL(HEALTH_URL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(8000);
            conn.setRequestMethod("GET");
            return conn.getResponseCode() == 200;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean isTomcatProcessAlive() {
        try {
            Process ps = Runtime.getRuntime().exec(new String[]{"sh", "-c", "ps -aux | grep " + PROCESS_GREP + " | grep -v grep"});
            try (BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()))) {
                return br.readLine() != null;
            }
        } catch (Exception e) {
            return false;
        }
    }

    private static void executeScript(String script, String successKeyword) {
        try {
            Process p = Runtime.getRuntime().exec(new String[]{"sh", "-c", script});
            try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
                String line;
                while ((line = br.readLine()) != null) {
                    if (line.contains(successKeyword)) {
                        System.out.println("[" + new Date() + "] 腳本輸出: " + line.trim());
                        return;
                    }
                }
            }
            p.waitFor();
        } catch (Exception e) {
            System.err.println("[" + new Date() + "] 執行腳本失敗: " + e.getMessage());
        }
    }

    private static void killProcess(String grepPattern) {
        try {
            String cmd = "ps -aux | grep " + grepPattern + " | grep -v grep | awk '{print $2}' | xargs kill -9";
            Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd});
        } catch (Exception e) {
            System.err.println("[" + new Date() + "] 強制殺死失敗: " + e.getMessage());
        }
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

部署與運維關鍵點

  1. 健康檢查端點: 在 Tomcat 應用中暴露 /health(Spring Boot Actuator、自定義 Servlet 均可),回傳 200 且體驗極快(不查 DB、不呼叫外部服務)。
  2. 以獨立 JVM 運行: 將 Watchdog 打包為可執行 JAR,配置為 Systemd Service / Supervisord / Windows Service,**與 Tomcat 進程完全隔離**,避免同進程假死導致守護失效。
  3. 日誌輸出: 重定向至檔案並配置 Logrotate,支援 ELK 採集。
  4. 告警通知: 整合 Email、DingTalk、Enterprise WeChat Webhook,重啟事件即時通知運維。
  5. 權限: 執行腳本需具備 Tomcat 檔案讀寫權限,建議以專用用戶 (如 tomcat) 運行。

系統級替代方案對比

進階擴充建議

  • 引入 **指數退避重試**:連續失敗延長檢查間隔,避免頻繁重啟加劇負載。
  • 整合 **JMX 監控**:讀取 Tomcat 記憶體、執行緒池、請求佇列指標,實現「預防性重啟」(如 Old GC 過頻、執行緒耗盡)。
  • 支援 **多實例監控**:配置檔案化管理多個 Tomcat 實例。
  • 遷移至 **Spring Boot Actuator + Kubernetes**:長期演進方向。

雖然現代架構傾向 K8s 原生自愈,但在傳統 VM 部署環境下,一個健壯的 Java 級 Tomcat 守護進程仍是低成本、高可靠的「最後一道防線」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方案優點缺點推薦場景
Systemd Restart=on-failure原生、零代碼、管理整個服務單元無法檢測「進程在但埠口無響應」假死簡單崩潰重啟
Supervisord跨平台、Web UI、進程組管理同 Systemd,無 HTTP 層檢查需管理多個異構進程
K8s Liveness/Readiness Probe原生 HTTP/TCP/Exec 探針、自動重建 Pod、滾動更新需容器化環境雲原生/K8s 部署
Zabbix/Prometheus + Alertmanager完整監控體系、圖表、多維告警架構複雜、非輕量級企業級統一監控平台
本文 Java Watchdog純 Java、無外部依賴、邏輯可完全自定義、適合傳統 VM 部署需自行維護代碼、單點故障傳統機房/VM、無 K8s、需 HTTP 層自愈