双机双nginx通过心跳包实现主备切换

最近在项目中受项目客观原因制约,构建了双机双nginx通过心跳包实现主备切换方案,以作记录。

需求效果:用户只有两台服务器,为用户提供唯一访问地址。

大致思路:分别在两台服务器上部署nginx,nginx的配置文件对外的地址及端口均相同,对应下层的服务器的地址为各自项目的地址,通过心跳包实现相互监听,动态控制项目内定时任务,以及两个nginx服务的开启与关闭。

具体代码实现如下:

 

package com.justner;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import javax.annotation.PostConstruct;

/**
 * 心跳实现主备切换
 * @author Justner
 * @version 2017-12-27
 */
public class HeartbeatService{
	
	private static int port = 2001; // 默认端口
	private static int times = 0; // 默认连续累计侦测异常次数
	private static int probeTimes = 5; // 默认侦测次数
	private static boolean isMaster = true; // 主备标识
	private static boolean isFirst = true; // 首次侦听标识
	private static InetAddress address; //远程服务器地址
	private static DatagramSocket socket; //远程socket
	private static InetAddress localAddress; //本地服务器地址
	private static DatagramSocket localSocket; //本地socket

	@PostConstruct
	public static void start() {
		// 初始化心跳服务
		init();
		// 向远程节点发送心跳
		startSenderThread();
		// 接收远程节点发来的心跳信息
		startRecvThread();
	}

	private static void init() {
		/**
		 * 创建与远程主机的UDP通道
		 */
		try {
			address = InetAddress.getByName("192.168.3.9");
			socket = new DatagramSocket(); // 创建套接字
			socket.setSoTimeout(1000); //单位ms
		} catch (UnknownHostException | SocketException e1) {
			e1.printStackTrace();
		}
		
		/**
		 * 本地的心跳监听端口
		 */
		try {
			localAddress = InetAddress.getLocalHost();
			localSocket = new DatagramSocket(port, localAddress);
		} catch (UnknownHostException | SocketException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 发送数据
	 */
	private static void startSenderThread() {
		// 初始化数据连接
		new Thread() {
			@Override
			public void run() {
				// 创建发送方的数据报信息
				String context = "...";
				DatagramPacket dataGramPacket = new DatagramPacket(context.getBytes(), context.getBytes().length, address, port);
				// 持续发送星跳信息
				while (true) {
					try {
						socket.send(dataGramPacket);//通过套接字发送数据
						doResultInfo(socket);
						// 如果正常收到心跳信息,重置心跳数据标识
						times = 0;
						// 如果是首次侦听收到心跳回复、则表明当前集群中已有活动主机,则当前主机为从机
						if (isFirst) {
							setMaster(false);
							isFirst = false;
						}
					} catch (IOException e) {
						// 当侦听异常时,则监测侦听失败次数。默认5次,5秒内未收到请求,将当前节点设置为主机
						times = times + 1;
						if (times >= probeTimes) {
							setMaster(true);
							// 非首次之后
							if (isFirst) {
								isFirst = false;
							}
						}
					}
					// 休眠进行下一次侦听
					try {
						Thread.sleep(1000); //单位ms
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// 打印前主机状态
					if (isMaster) {
						System.out.println("=========心跳标识:主机");
					} else {
						System.out.println("=========心跳标识:从机");
					}
				}
			}
		}.start();
	}

	private static void doResultInfo(DatagramSocket socket) throws IOException {
		byte[] backbuf = new byte[1024];
		DatagramPacket backPacket = new DatagramPacket(backbuf, backbuf.length);
		socket.receive(backPacket);
		// 打印心跳记录
		String getMsg = new String(backbuf, 0, backPacket.getLength());
		if ("......".equals(getMsg)) {
			times = 0;
			System.out.println("客户端返回的的数据为:" + getMsg);
		} else if ("....".equals(getMsg)) {
			setMaster(false);
			System.out.println("===========主备切换成功!============");
		}
	}

	/**
	 * 接收返回消息
	 */
	private static void startRecvThread() {
		new Thread() {
			public void run() {
				try {
					byte[] buf = new byte[1024]; // 定义byte数组
					DatagramPacket packet = new DatagramPacket(buf, buf.length); // 创建DatagramPacket对象
					while (true) {
						localSocket.receive(packet); // 通过套接字接收数据
						String getMsg = new String(buf, 0, packet.getLength());
						System.out.println("客户端发送的数据为:" + getMsg);
						String reContext = "";
						if ("...".equals(getMsg)) {
							reContext = ".....";
						} else if ("..".equals(getMsg)) {
							setMaster(true);
							reContext = "....";
						}
						SocketAddress sendAddress = packet.getSocketAddress();
						byte[] backbuf = reContext.getBytes();
						DatagramPacket sendPacket = new DatagramPacket(backbuf,backbuf.length, sendAddress); // 封装返回给客户端的数据
						localSocket.send(sendPacket); // 通过套接字反馈服务器数据
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}
	
	/**
	 * 执行业务更改
	 * @param stauts
	 */
	private static void setMaster(boolean stauts) {
		// 主机
		if ((stauts && !isMaster) || (stauts && isFirst)) {
			try {
				/*这里处理项目的具体业务,针对定时任务可以设置全局内存变量来判断执行*/
				nginxTurn("nginxStart.bat"); //cmd批处理启动nginx命令,脚本文件存于项目中
				System.out.println("启动定时任务||设置主机成功");
			} catch (Exception e) {
				System.out.println("启动定时任务||设置主机失败");
			}
		// 备机
		} else if((!stauts && isMaster) || (!stauts && isFirst)){
			try {
				/*这里处理项目的具体业务,针对定时任务可以设置全局内存变量来判断执行*/
				nginxTurn("nginxQuit.bat"); //cmd批处理退出nginx命令,脚本文件存于项目中
				System.out.println("停止定时任务||设置备机成功");
			} catch (Exception e) {
				System.out.println("停止定时任务||设置备机失败");
			}
		}
		isMaster = stauts;
	}
	
	/**
	 * 手动调用更改主备
	 * @return
	 * @throws IOException 
	 */
	public static boolean setOtherMaster() throws IOException  {
		// 创建发送方的数据报信息
		String context = "..";
		DatagramPacket dataGramPacket = new DatagramPacket(context.getBytes(),context.getBytes().length, address, port);
		// 接收返回数据的数据包
		socket.send(dataGramPacket);
		doResultInfo(socket);
		return !isMaster;
	}
	
	/**
	 * nginx开启关闭
	 * @param command
	 * command命令为bat脚本或者sh脚本文件,此处为调用脚本文件开启或者关闭nginx服务
	 * 脚本命令:启动start nginx,退出nginx -s quit
	 */
	public static void nginxTurn(String command){
		try {
			//执行命令
			String cmd = "cmd /c start " + command; //Windows命令语句
//			String cmd = "chmod 777 /" + command; //linux命令语句
			Process process = Runtime.getRuntime().exec(cmd);
			InputStream ins = process.getInputStream(); // 获取执行cmd命令后的信息
			BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
			String line = null;
			while ((line = reader.readLine()) != null) {
				System.out.println(line); // 输出
			}
			int exitValue = process.waitFor();
			System.out.println("返回值:" + exitValue);
			//关闭流
			process.getOutputStream().close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

你可能感兴趣的:(双机双nginx通过心跳包实现主备切换)