1. 程式人生 > >Windows下雙守護程序(spring boot 版本)

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

有疑問可以聯絡我