一、簡介
現在的伺服器端程式很多都是基於Java開發,針對於Java開發的Socket程序,這樣的伺服器端上線後出現問題需要手動重啟,萬一大半夜的掛了,還是特別麻煩的。
大多數的解決方法是使用其他進程來守護伺服器程序,如果伺服器程式掛了,透過守護進程來啟動伺服器程式。
萬一守護程式掛了呢?使用雙守護來提高穩定性,守護A負責監控伺服器程式與守護B,守護B負責監控守護A,任何一方出現問題,都能快速的啟動程序,提升伺服器程式的穩定性。
Java的運行環境不同於C等語言開發的程序,Java程式跑在JVM上面。有別於C語言可以直接創建流程,Java創建一個程序等同於使用java -jar xxx.jar啟動一個程式。
Java啟動程序並沒有C#類似的單一實例限制,你可以啟動多個,但是你不能啟動多個,不能讓多個守護A去守護伺服器程序,萬一啟動了多個伺服器程式怎麼辦?
二、技術講解
這裡的技術講解比較粗略,具體請百度一下,這裡只講解作用。
1、jps命令。
JDK自帶的指令工具,使用jps -l可以列出正在執行的Java程序,顯示Java程式的pid與Name。只對Java程式有效,其實看的是運行的JVM
2.java.nio.channels.FileLock類的使用這個是Java new IO中的類,使用他可以維持在讀取文件的給文件加上鎖,判斷文件時候有鎖可以判斷該文件是否被其他的程序使用
3、ProcessBuilder與Process
這兩個原理差不多,都是呼叫系統的命令運行,然後回傳訊息。但是硬編碼會導致你的Java程式失去可移植性,可以將指令獨立到設定檔。
三、設計原理
Server:伺服器程式
A:守護程式A
B:守護程式B
A.lock:守護程式A的檔案鎖
B.lock:守護程式B的檔案鎖
-------------------------------------------------- --------------------------------
Step 1:首先不考慮Server,只考慮A與B之間的守護
1.A判斷B是否存活,沒有就啟動B
2.B判斷A是否存活,沒有就啟動A
3.在運作過程中A與B互相去拿對方的文件鎖,如果拿到了,證明對面掛了,則啟動對方。
4.A啟動的時候,取得A.lock檔案的鎖,如果拿到了證明沒有A啟動,則A運行;如果沒有拿到鎖,證明A已經啟動了,或者是B判斷的時候拿到了鎖,如果是A已經啟動了,不需要再啟動A,如果是B判斷的時候拿到了鎖,沒關緊要,反正B會再啟動A。
5.B啟動的時候原理與A一致。
6.運作中如果A掛了,B判斷到A已經掛了,則啟動A。 B同理。
Step 2:加入Server
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都掛了,守護結束
Step 3:使用Shutdown結束守護,不然結束Server後會自動啟動
四、實現
1、GuardA的實現
複製代碼代碼如下:
public class GuardA {
// GuardA用於維持自己的鎖
private File fileGuardA;
private FileOutputStream fileOutputStreamGuardA;
private FileChannel fileChannelGuardA;
private FileLock fileLockGuardA;
// GuardB用於偵測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();
fileLockGuardA = fileChannelGuardA.tryLock();
if (fileLockGuardA == null) {
System.exit(0);
}
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、GuardServer的實現
複製代碼代碼如下:
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);
//將命令分開
// String[] cmds = cmd.split(" ");
// ProcessBuilder builder = new ProcessBuilder(cmds);
//
ProcessBuilder builder=new ProcessBuilder(new String[]{"/bin/sh","-c",cmd});
//將伺服器程式的輸出定位到/dev/tty
builder.redirectOutput(new File("/dev/tty"));
builder.redirectError(new File("/dev/tty"));
builder.start(); // throws IOException
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;
}
}
3、GuardAMain實現
複製代碼代碼如下:
public class GuardAMain {
public static void main(String[] args) throws Exception {
GuardA guardA = new GuardA();
Configure configure = new Configure();
GuardServer server = new GuardServer(configure.getServername());
while (true) {
// 如果GuardB未運行運行GuardB
if (!guardA.checkGuardB()) {
System.out.println("Start GuardB.....");
Runtime.getRuntime().exec(configure.getStartguardb());
}
// 偵測伺服器存活
if (server.checkServer() <= 0) {
boolean isServerDown = true;
// trip check
for (int i = 0; i < 3; i++) {
// 如果服務是存活著
if (server.checkServer() > 0) {
isServerDown = false;
break;
}
}
if (isServerDown)
server.startServer(configure.getStartserver());
}
Thread.sleep(configure.getInterval());
}
}
}
4、Shutdown實現
複製代碼代碼如下:
public class ShutDown {
public static void main(String[] args) throws Exception {
Configure configure = new Configure();
System.out.println("Shutdown Guards..");
for (int i = 0; i < 3; i++) {
Process p = Runtime.getRuntime().exec("jps -l");
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.toLowerCase().contains("Guard".toLowerCase())) {
String[] strings = line.split("//s{1,}");
int pid = Integer.parseInt(strings[0]);
Runtime.getRuntime().exec(configure.getKillcmd() + " " + pid);
}
}
p.waitFor();
reader.close();
p.destroy();
Thread.sleep(2000);
}
System.out.println("Guards is shutdown");
}
}
5.GuardB與GuardA類似
五、下載與使用
專案資料夾:guard_demo
下載網址:http://pan.baidu.com/s/1bn1Y6BX
如果有任何疑問或建議,請聯絡我