Windows下雙守護程序(spring boot 版本)
一、簡介
現在的伺服器端程式很多都是基於Java開發,針對於Java開發的Socket程式,這樣的伺服器端上線後出現問題需要手動重啟,萬一大半夜的掛了,還是特別麻煩的。
大多數的解決方法是使用其他程序來守護伺服器程式,如果伺服器程式掛了,通過守護程序來啟動伺服器程式。
萬一守護程序掛了呢?使用雙守護來提高穩定性,守護A負責監控伺服器程式與守護B,守護B負責監控守護A,任何一方出現問題,都能快速的啟動程式,提高伺服器程式的穩定性。
Java的執行環境不同於C等語言開發的程式,Java程式跑在JVM上面。不同於C語言可以直接建立程序,Java建立一個程序等同於使用java -jar xxx.jar啟動一個程式。
Java啟動程式並沒有C#類似的單例項限制,你可以啟動多個,但是你不能啟動多個,不能讓多個守護A去守護伺服器程式,萬一啟動了多個伺服器程式怎麼辦?
二.涉及技術
1. jps命令
jps位於jdk的bin目錄下,其作用是顯示當前系統的java程序情況,及其id號。 jps相當於Solaris程序工具ps。不象”pgrep java”或”ps -ef grep java”,jps並不使用應用程式名來查詢JVM例項。因此,它查詢所有的Java應用程式,包括即使沒有使用java執行體的那種(例如,定製的啟動 器)。另外,jps僅查詢當前使用者的Java程序,而不是當前系統中的所有程序。
-q 只顯示pid,不顯示class名稱,jar檔名和傳遞給main 方法的引數。
-m 輸出傳遞給main 方法的引數,在嵌入式jvm上可能是null。
-l 輸出應用程式main class的完整package名 或者 應用程式的jar檔案完整路徑名。
-v 輸出傳遞給JVM的引數。
2. java.nio.channels.FileLock檔案鎖類的使用
FileLock是java 1.4 版本後出現的一個類,它可以通過對一個可寫檔案(w)加鎖,保證同時只有一個程序可以拿到檔案的鎖,這個程序從而可以對檔案做訪問;而其它拿不到鎖的程序要麼選擇被掛起等待,要麼選擇去做一些其它的事情, 這樣的機制保證了眾程序可以順序訪問該檔案。也可以看出,能夠利用檔案鎖的這種性質,在一些場景下,雖然我們不需要操作某個檔案, 但也可以通過 FileLock 來進行併發控制,保證程序的順序執行,避免資料錯誤。本程式中使用它可以維持在讀取檔案的同時給檔案加上鎖,判斷檔案時候有鎖可以判斷該檔案是否被其他的程式使用。
3. Java Runtime.exec()的使用
用Java編寫應用時,有時需要在程式中呼叫另一個現成的可執行程式或系統命令。比如用法Runtime.getRuntime.exec("notepad"),執行這個Java程式,就會執行記事本程式。同理,只需修改那個引數就可以執行其他的一些程式,也可以進行一些操作,比如關機。本程式中使用這一命令呼叫批處理檔案啟動java程式。
三.設計原理
Server:伺服器程式
GuardA:守護程序A
GuardB:守護程序B
C:\\java\\A.txt:守護程序A的檔案鎖
C:\\java\\B.txt:守護程序B的檔案鎖
A和B之間的守護
1.A判斷B是否存活,沒有就啟動B
2.B判斷A是否存活,沒有就啟動A
3.在執行過程中A與B互相去拿對方的檔案鎖,如果拿到了,證明對面掛了,則啟動對方。
4.A啟動的時候,獲取C:\\java\\A.txt檔案的鎖,如果拿到了證明沒有A啟動,則A執行;如果沒有拿到鎖,證明A已經啟動了,或者是B判斷的時候拿到了鎖,如果是A已經啟動了,不需要再次啟動A,如果是B判斷的時候拿到了鎖,問題不大,反正B會再次啟動A。
5.B啟動的時候原理與A一致。
6.執行中如果A掛了,B判斷到A已經掛了,則啟動A。B同理。
守護伺服器程式
1.A用於守護B和Server,B用於守護A。
2.原理與Step 1 一致,只是A多個一個守護Serer的任務。
3.當A執行的時候,使用程序pid檢測到Server已經掛了,就啟動Server
4.如果Server與A都掛了,B會啟動A,然後A啟動Server
5.如果Server與B掛了,A啟動Server與B
6.如果A與B都掛了,守護結束
四.類的實現
1.GuardA
package com.zzc.guard.a;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import com.zzc.config.Configure;;
public class GuardA {
// GuardA用於維持自己的鎖
private File fileGuardA;
private FileOutputStream fileOutputStreamGuardA;
private FileChannel fileChannelGuardA;
private FileLock fileLockGuardA;
// GuardA用於檢測B的鎖
private File fileGuardB;
private FileOutputStream fileOutputStreamGuardB;
private FileChannel fileChannelGuardB;
private FileLock fileLockGuardB;
public GuardA() throws Exception {
fileGuardA = new File(Configure.GUARD_A_LOCK);
if (!fileGuardA.exists()) {
fileGuardA.createNewFile();//檔案不存在,建立新檔案
}
//獲取檔案鎖,拿不到證明GuardA已啟動則退出
fileOutputStreamGuardA = new FileOutputStream(fileGuardA);
fileChannelGuardA = fileOutputStreamGuardA.getChannel();
//tryLock() 是非阻塞式的,它設法獲取鎖,但如果不能獲得,例如因為其他一些程序已經持有相同的鎖,
//而且不共享時,丟擲檔案重疊鎖異常【OverlappingFileLockException】
fileLockGuardA = fileChannelGuardA.tryLock();
if (fileLockGuardA == null) {
System.exit(0);//終止jvm,正常退出java程式
}
fileGuardB = new File(Configure.GUARD_B_LOCK);
if (!fileGuardB.exists()) {
fileGuardB.createNewFile();
}
fileOutputStreamGuardB = new FileOutputStream(fileGuardB);
fileChannelGuardB = fileOutputStreamGuardB.getChannel();
}
/**
* 檢測B是否存在
*
* @return true B已經存在
*/
public boolean checkGuardB() {
try {
fileLockGuardB = fileChannelGuardB.tryLock();
if (fileLockGuardB == null) {
return true;
} else {
fileLockGuardB.release();
return false;
}
} catch (IOException e) {
System.exit(0);
// never touch
return true;
}
}
}
2.配置類condifure
package com.zzc.config;
public class Configure {
public static final String GUARD_B_LOCK = "C:\\java\\A.txt";
public static final String GUARD_A_LOCK = "C:\\java\\B.txt";
//得到被守護程序的程序名
public String getServername() {
String serverName = null;
//守護web伺服器
serverName = "ROOM.jar";
return serverName;
}
//執行被守護程序的路徑
public String getStartserver() {
String file = "C:\\java\\Webserver.bat";
String Cmd = "cmd /c start "+file.replaceAll(" ", "\" \"");
return Cmd;
}
//睡眠時間
public long getInterval() {
// TODO Auto-generated method stub
return 5000;
}
public String getStartguardb() {
String file = "C:\\java\\DaemonB.bat";
String cmd ="cmd /c start "+file.replaceAll(" ", "\" \"");
return cmd;
}
}
3.主類GuardAMain
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.zzc.config.Configure;
public class GuardAMain {
public static void main(String[] args) throws Exception {
System.out.println("GuardA已啟動..."+getStringDate());
GuardA guardA = new GuardA();
Configure configure = new Configure();
GuardServer server = new GuardServer(configure.getServername());
while (true) {
// 如果GuardB未執行 執行GuardB
if (!guardA.checkGuardB()) {
System.out.println("GuardB掛掉了,啟動GuardB...."+getStringDate());
Process process = Runtime.getRuntime().exec(configure.getStartguardb());
printMessage(process.getInputStream());
printMessage(process.getErrorStream());
int value = process.waitFor();
System.out.println(value);
}
// 檢測伺服器存活
if (server.checkServer() <= 0) {
boolean isServerDown = true;
// trip check
for (int i = 0; i < 4; i++) {
// 如果服務是存活著
if (server.checkServer() > 0) {
isServerDown = false;
break;
}
}
if (isServerDown)
System.out.println("web伺服器掛掉了,啟動web"+getStringDate());
server.startServer(configure.getStartserver());
}
Thread.sleep(configure.getInterval());
}
}
/*
* 開兩個執行緒分別去處理標準輸出流和錯誤輸出流
* 處理緩衝區的資訊,防止程序的輸出資訊量很大導致程式阻塞
*/
private static void printMessage(final InputStream input) {
new Thread (new Runnable() {
@Override
public void run() {
Reader reader = new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while((line=bf.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 獲取現在時間
*
* @return返回字串格式 yyyy-MM-dd HH:mm:ss
*/
public static String getStringDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = formatter.format(currentTime);
return dateString;
}
}
4.類GuardServer
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
public class GuardServer {
private String servername;
public GuardServer(String servername) {
this.servername = servername;
}
public void startServer(String cmd) throws Exception {
System.out.println("Start Server : " + cmd);
Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());
printMessage(process.getErrorStream());
int value = process.waitFor();
System.out.println(value);
Thread.sleep(10000);
}
/**
* 檢測服務是否存在
*
* @return 返回配置的java程式的pid
* @return pid >0 返回的是 pid <=0 代表指定java程式未執行
* **/
public int checkServer() throws Exception {
int pid = -1;
Process process = null;
BufferedReader reader = null;
process = Runtime.getRuntime().exec("jps -l");
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
String[] strings = line.split("\\s{1,}");
if (strings.length < 2)
continue;
if (strings[1].contains(servername)) {
pid = Integer.parseInt(strings[0]);
break;
}
}
reader.close();
process.destroy();
return pid;
}
/*
* 開兩個執行緒分別去處理標準輸出流和錯誤輸出流
* 處理緩衝區的資訊,防止程序的輸出資訊量很大導致程式阻塞
*/
private static void printMessage(final InputStream input) {
new Thread (new Runnable() {
@Override
public void run() {
Reader reader = new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while((line=bf.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
5.類GuardB
import java.io.File;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import com.zzc.config.Configure;
public class GuardB {
//GuardB用於維持自己的鎖
private File fileGuardB;
private FileOutputStream fileOutputStreamGuardB;
private FileChannel fileChannelGuardB;
private FileLock fileLockGuardB;
//GuardB用於檢測A的鎖
private File fileGuardA;
private FileOutputStream fileOutputStreamGrardA;
private FileChannel fileChannelGrardA;
private FileLock fileLockGrardA;
public GuardB() throws Exception{
fileGuardB = new File(Configure.GUARD_B_LOCK);
if(!fileGuardB.exists()){
fileGuardB.createNewFile();
}
//獲取檔案鎖,拿不到證明GuardA已啟動則退出
fileOutputStreamGuardB = new FileOutputStream(fileGuardB);
fileChannelGuardB = fileOutputStreamGuardB.getChannel();
fileLockGuardB = fileChannelGuardB.tryLock();
if(fileLockGuardB == null){
System.exit(0);
}
fileGuardA = new File(Configure.GUARD_A_LOCK);
if(!fileGuardA.exists()){
fileGuardA.createNewFile();
}
fileOutputStreamGrardA = new FileOutputStream(fileGuardA);
fileChannelGrardA = fileOutputStreamGrardA.getChannel();
}
/**
* 檢測B是否存在
*
* @return true B已經存在
*/
public boolean checkGuardA() {
try {
fileLockGrardA = fileChannelGrardA.tryLock();
if(fileLockGrardA == null){
return true;
} else {
fileLockGrardA.release();
return false;
}
} catch (Exception e) {
System.exit(0);
return true;
}
}
}
6.GuardB的配置類configre
package com.zzc.config;
public class Configure {
public static final String GUARD_B_LOCK = "C:\\java\\A.txt";
public static final String GUARD_A_LOCK = "C:\\java\\B.txt";
//睡眠時間
public long getInterval() {
// TODO Auto-generated method stub
return 5000;
}
public String getStartguarda() {
String file = "C:\\java\\DaemonA.bat";
String cmd = "cmd /c start "+file.replaceAll(" ", "\" \"");
return cmd;
}
}
7.GuardB的主類GuardBMain
package com.zzc.guard.b;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.zzc.config.Configure;
public class GuardBMain {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
System.out.println("GuardB已啟動...."+getStringDate());
GuardB guardB = new GuardB();
Configure configure = new Configure();
while(true){
//如果GuardA掛掉了,執行GuardA
if(!guardB.checkGuardA()){
System.out.println("GuardA掛掉了,啟動GuardA...."+getStringDate());
Process process = Runtime.getRuntime().exec(configure.getStartguarda());
printMessage(process.getInputStream());
printMessage(process.getErrorStream());
int value = process.waitFor();
System.out.println(value);
}
Thread.sleep(configure.getInterval());
}
}
/*
* 開兩個執行緒分別去處理標準輸出流和錯誤輸出流
* 處理緩衝區的資訊,防止程序的輸出資訊量很大導致程式阻塞
*/
private static void printMessage(final InputStream input) {
new Thread (new Runnable() {
@Override
public void run() {
Reader reader = new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while((line=bf.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 獲取現在時間
*
* @return返回字串格式 yyyy-MM-dd HH:mm:ss
*/
public static String getStringDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = formatter.format(currentTime);
return dateString;
}
}
使用的時候就在c盤新建一個名為java的檔案,
將webServer.bat檔案拷貝到C/java路徑下,預設啟動的jar檔名為ROOM.jar。將DaemonA.jar DaemonB.jar Daemona.bat DaemonB.bat 拷貝到C/java路徑下,雙擊啟動DaemonB.bat即可實現對伺服器程序的守護。
spring mvc 版本的大同小異,使用的檔案以及說明都放在了github:https://github.com/13273455064/Java-dual-daemon-for-Windows
有疑問可以聯絡我