問題描述
工作中遇到這樣一個問題,我們的應用為了實現高可用會采取雙機部署,拓撲圖大致如下:
這種方案可以簡單的保證高可用,即便應用服務器或者數據庫服務器其中一台宕機,整個系統的功能還是不會受到影響,但是這里會出現一個問題:例如當應用服務器1宕機了,所有的負載集中到應用服務器2上以后,因為系統功能是正常的,而作為產品交付客戶后也沒有現場的實施或運維人員在,我們並不知道有一台服務器已經宕機了,所以,我們要實現一個簡單的監控,去查看每個服務器上的每個應用程序是否正常的啟動着,當異常的時候可以通過網頁重新啟動一下.
實現思路
在我們的數據庫中建立兩張表,一張服務器表,一張應用表,服務器表作為主表
在服務器對應位置為每一個需要監控的應用放置一個監控的SHELL腳本,一個重啟的SHELL腳本
在WEB服務中增加定時任務,采用輪詢的方式執行監控腳本,如果返回值正常,則更新應用表中的狀態字段,同時在WEB頁面中加入一個監控頁面,可以在應用狀態異常的通過按鈕點擊完成重啟操作
技術要點
基本的增刪改查並沒有什么問題,在我的設計思路中有兩個點需要注意
- SHELL腳本的編寫
這個領域我並不了解,交由負責實施的同事解決 - JAVA任務調度
這部分我可能會單獨寫一篇博客來記錄一下我自己學習Spring Boot中的 @Scheduled注解和比較常用的Quartz框架的過程和對比 - JAVA鏈接服務器並執行SHELL
這里我采用的依賴包是:
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
</dependency>
在有了這個依賴包之后,我就只用提供兩個共通的工具方法就可以了,一個是登錄一下服務器,看看服務器是否宕機,一個是執行SELL腳本的方法,根據SHELL的返回值確定監控的服務是否正常的方法就好了
代碼實現
因為執行SHELL腳本的時候可能是給出正常的返回值,也可能是異常的信息,這里需要一個JAVA Bean來接收這兩個消息,代碼如下:
import java.io.Serializable;
/**
* shell腳本執行結果<br/>
* 先通過getSuccess方法判斷命令是否執行成功<br/>
* 執行成功的時候采用result作為返回值<br/>
* 執行不成功采用errorOut作為返回值<br/>
*
* @author weizj
*/
public class ShellResult implements Serializable {
private static final long serialVersionUID = -110281463872334425L;
/** 腳本輸出結果 */
private String result;
/** 異常輸出結果 */
private String errorMsg;
/** 回話退出狀態 */
private int exitStatus;
public ShellResult() {
}
public ShellResult(String result, String errorOut, int exitStatus) {
this.result = result.trim();
this.errorMsg = errorOut.trim();
this.exitStatus = exitStatus;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result.trim();
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg.trim();
}
public int getExitStatus() {
return exitStatus;
}
public void setExitStatus(int exitStatus) {
this.exitStatus = exitStatus;
}
/** 是否成功關閉會話 */
public boolean getSuccess() {
return this.exitStatus == 0;
}
}
工具類方法代碼如下:
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* java執行shell腳本的工具類
*
* @author weizj
*/
public class GanymedUtils {
private static final Logger logger = LoggerFactory.getLogger(GanymedUtils.class);
/** 超時時間 */
private static final int TIME_OUT = 1000 * 5 * 60;
/**
* 登錄遠端服務器
*
* @param ip 主機地址
* @param userName 用戶名
* @param password 密碼
* @return 當前的連接
* @throws IOException
*/
public static Connection login(String ip, String userName, String password) throws IOException {
Connection connection = new Connection(ip);
connection.connect();
return connection.authenticateWithPassword(userName, password) ? connection : null;
}
/**
* 執行一個命令
*
* @param ip 主機ip
* @param userName 用戶名
* @param password 密碼
* @param scripts 需要執行的腳本
* @param charset 字符編碼
* @return ShellResult類
* @throws Exception
*/
public static ShellResult exec(String ip, String userName, String password, String scripts, Charset charset) throws IOException {
Connection connection = login(ip, userName, password);
if (connection == null) {
throw new RuntimeException("登錄遠程服務器出現異常,ip為:" + ip);
}
// Open a new {@link Session} on this connection
Session session = connection.openSession();
try (InputStream stdOut = new StreamGobbler(session.getStdout()); InputStream stdErr = new StreamGobbler(session.getStderr())) {
// Execute a command on the remote machine.
session.execCommand(scripts);
String outStr = processStream(stdOut, charset.name());
String outErr = processStream(stdErr, charset.name());
session.waitForCondition(ChannelCondition.EXIT_STATUS, TIME_OUT);
int exitStatus = session.getExitStatus();
return new ShellResult(outStr, outErr, exitStatus);
}
}
/**
* 執行腳本
*
* @param in 輸入流
* @param charset 字符編碼
* @return
* @throws IOException
*/
private static String processStream(InputStream in, String charset) throws IOException {
byte[] buf = new byte[1024];
StringBuilder sb = new StringBuilder();
while (in.read(buf) != -1) {
sb.append(new String(buf, charset));
}
return sb.toString();
}
public static void main(String[] args) {
try {
ShellResult status = exec("10.0.0.1", "root", "root", "ipconfig", StandardCharsets.UTF_8);
System.out.println(">>>>>>Result>>>>>>>");
System.out.println(status.getResult());
System.out.println(">>>>>>ErrorMsg>>>>>>>>");
System.out.println(status.getErrorMsg());
} catch (IOException e) {
e.printStackTrace();
}
}
}
通過這兩個類再配合任務調度就可以基本的實現對應用的監控了.