java:如何讓程序按要求自行重啟?


正文開始前的廢話:
這里的程序即包括b/s的web application,也包括standalone的類c/s的java application。
 
為什么要自我重啟?
 
場景1:分布式環境中,一般會有很多應用(即包括c/s的java application,又有b/s的web application)部署在不同的環境中,為了管理方便,通常會把一些公用的配置,比如:報警發郵件用的郵箱賬號/密碼/smtp信息,公用的ftp賬號信息,甚至jdbc的連接串信息等,統一放在某個位置(共享的網絡存儲目錄、redis緩存、database、zookeeper、遠程service中均可),這樣管理起來比較方便。象郵箱賬號這些信息還好,各應用訂閱配置的變化,發現變化時,如果是spring環境,直接調用applicationContext.refresh(),配置就會重新刷新。但是對於數據源這種特殊配置,就比較難弄了,要考慮連接池中已經連接成功的connection對象,已經通過舊的datasource查出來的數據,跟舊datasource關聯的sqlSesstionFactory,Mapper實例等等,要全部換血,很難保證,最好的辦法就是讓程序重啟。
 
場景2:寫程序嘛,有隱藏的bug在所難免,絕對零bug的程序還是很罕見的,如果隨着程序運行時間的不斷增加,程序性能越來越差或假死,需要重啟一下,通常需要遠程連撞到linux,敲命令kill進程,再重啟java application,這對於不熟悉linux的新手管理人員,一來可能比較陌生,二來未必有執行權限,所以通過一個友好的監控管理界面,點擊下重啟按鈕,讓指定的程序重啟,會更容易讓人接受。
 
正文開始:
 
一、程序如何知道自己需要重啟?
顯然,如果有一個程序,用戶想正常關閉的時候,程序又自動重啟,如此循環,這就成關不掉的惡意程序了。 
所以,程序應該由單獨的進程監聽並接收特定的指令,而不影響用戶正常關閉程序,思路: 
程序啟動時,生成一個唯一的uuid(或其它標識,只要保證全局唯一就行),然后向zookeeper注冊一個臨時節點。 
比如:
/app/uuid-1
這樣監控中心,只要掃描/app下有多少臨時節點,就知道當前運行了哪些應用。 
管理員從監控中心希望將某個應用重啟時,可以向zookeeper寫一個節點
/command/uuid-1 節點的內容,約定為:restart 
應用啟動后,監聽/command/uuid-1 節點的數據變化,一旦發現有restart的數據內容,即認為收到了重啟指令,然后就按下面的處理,自我毀滅,重新投胎轉世。
 
 
二、java application的重啟 
網上的樣例代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
Runtime.getRuntime().addShutdownHook( new  Thread() {
     public  void  run() {
         try  {
             String restartCmd =  "nohup java -jar xxx.jar" ;
             Thread.sleep( 10000 ); //等10秒,保證分身啟動完成后,再關掉自己
             logger.debug( "程序重啟完成!" );
         catch  (Exception e) {
             logger.error( "重啟失敗,原因:" , e);
         }
     }
});
logger.debug( "程序准備重啟!" );
System.exit( 0 );
可以改進的地方: 
a) sleep(10000) 即等待10秒,等自己的『分身』啟動好以后,再把自己的『真身』給殺死。這里的10秒,其實也是拍腦袋定的,如果追求完美的話,理論上講,只要系統進程中出現了新啟動的『分身』,就可以將『真身』人道毀滅了。
 
問題:如果知道『分身』已經啟動完成?
答案:java可以獲取 jps -l 的輸出,知道當前所有的java進程,這樣就可以知道指定的應用有沒有啟動。可以在重啟前,獲取一次jps -l 的輸出,重啟后,再執行一次jps -l 的輸出,對比二次輸出,如果發現多出一個新的指定進程名,就表示『分身』啟動完成,可以結束自己。
 
附:java代碼獲取jps輸出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import  org.apache.logging.log4j.*;
import  java.io.BufferedReader;
import  java.io.InputStreamReader;
 
public  final  class  RuntimeUtil {
 
     private  static  Logger logger = LogManager.getLogger();
 
     public  static  String exec(String command) {
         StringBuilder sb =  new  StringBuilder();
         try  {
             Process process = Runtime.getRuntime().exec(command);
             BufferedReader reader =  new  BufferedReader( new  InputStreamReader(process.getInputStream()));
             String line =  null ;
             while  ((line = reader.readLine()) !=  null ) {
                 sb.append(line);
             }
             process.getOutputStream().close();
             reader.close();
             process.destroy();
         catch  (Exception e) {
             logger.error( "執行外部命令錯誤,命令行:"  + command, e);
         }
         return  sb.toString();
     }
 
     public  static  String jps() {
         return  exec( "jps -l" );
     }
}

  

b)  java -jar xxx.jar 這里也不太好,一是xxx.jar是字符串,編譯期發現不了錯誤,二是路徑是相對路徑,就算啟動成功了,最終jps顯示的進程名是jar,看不出實際對應的是啥程序(詳情可參考  設置 java -jar 的進程顯示名稱 ),可以代碼獲取當前程序的實際路徑
1
2
3
4
5
6
7
public  static  String getJarExecPath(Class clazz) {
     String path = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
     if  (OSUtil.getOSname().equals(OSType.Windows)) {
         return  path.substring( 1 );
     }
     return  path;
}

OSUtil代碼從這里獲取

  

三、 web application的重啟
這里只討論部署在jboss上的解決方案,
這二篇文章中,已經給出了用編碼或shell命令來控制jboss的方法,所以web application的按需重啟思路就有了:
從監控界面點擊『重啟』某個web application時,后台代碼先將該web application disable掉,然后再重新enable或assign


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM