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

有疑问可以联系我

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页