簡介
最近運維人員提出需求,增加一個運維頁面, 查詢當前的業(yè)務進程信息包括:進程名稱、啟動命令、啟動時間、運行時間等,可以通過頁面點擊重啟按鈕,可以重啟后端的一系列系統(tǒng)進程。
思路
JAVA程序獲取linux進程信息可以通過shell腳本獲取進程信息、通過讀取proc文件系統(tǒng)獲取進程信息。 但是為了系統(tǒng)的安全性、方便維護等角度出發(fā),更多的是java通過shell獲取和linux交互能力。
java程序中要執(zhí)行l(wèi)inux命令主要依賴2個類:Process和Runtime:
Process:
ProcessBuilder.start() 和 Runtime.exec 方法創(chuàng)建一個本機進程,并返回 Process 子類的一個實例, 該實例可用來控制進程并獲得相關(guān)信息。Process 類提供了執(zhí)行從進程輸入、執(zhí)行輸出到進程、等待進程完成、 檢查進程的退出狀態(tài)以及銷毀(殺掉)進程的方法。 創(chuàng)建進程的方法可能無法針對某些本機平臺上的特定進程很好地工作,比如,本機窗口進程,守護進程,Microsoft windows 上的 Win16/DOS 進程,或者 shell 腳本。創(chuàng)建的子進程沒有自己的終端或控制臺。它的所有標準 io(即 stdin、stdout 和 stderr) 操作都將通過三個流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父進程。 父進程使用這些流來提供到子進程的輸入和獲得從子進程的輸出。因為有些本機平臺僅針對標準輸入和輸出流提供有限的緩沖區(qū)大小, 如果讀寫子進程的輸出流或輸入流迅速出現(xiàn)失敗,則可能導致子進程阻塞,甚至產(chǎn)生死鎖。 當沒有 Process 對象的更多引用時,不是刪掉子進程,而是繼續(xù)異步執(zhí)行子進程。 對于帶有 Process 對象的 Java 進程,沒有必要異步或并發(fā)執(zhí)行由 Process 對象表示的進程。
- 特別需要注意的是:
1,創(chuàng)建的子進程沒有自己的終端控制臺,所有標注操作都會通過三個流
(getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父進程(父進程可通過這些流判斷子進程的執(zhí)行情況)
2,因為有些本機平臺僅針對標準輸入和輸出流提供有限的緩沖區(qū)大小,如果讀寫子進程的輸出流或輸入流迅速出現(xiàn)失敗,
則可能導致子進程阻塞,甚至產(chǎn)生死鎖
特別需要注意:如果子進程中的輸入流,輸出流或錯誤流中的內(nèi)容比較多,最好使用緩存(注意上面的情況2)
Runtime
每個Java應用程序都有一個Runtime類實例,使應用程序能夠與其運行的環(huán)境相連接。可以通過getRuntime方法獲取當前運行時環(huán)境。 應用程序不能創(chuàng)建自己的Runtime類實例。
Process exec(String command)
在單獨的進程中執(zhí)行指定的字符串命令。
Process exec(String command, String[] envp)
在指定環(huán)境的單獨進程中執(zhí)行指定的字符串命令。
Process exec(String command, String[] envp, File dir)
在有指定環(huán)境和工作目錄的獨立進程中執(zhí)行指定的字符串命令。
Process exec(String[] cmdarray)
在單獨的進程中執(zhí)行指定命令和變量。
Process exec(String[] cmdarray, String[] envp)
在指定環(huán)境的獨立進程中執(zhí)行指定命令和變量。
Process exec(String[] cmdarray, String[] envp, File dir)
在指定環(huán)境和工作目錄的獨立進程中執(zhí)行指定的命令和變量。
command:一條指定的系統(tǒng)命令。
envp:環(huán)境變量字符串數(shù)組,其中每個環(huán)境變量的設(shè)置格式為name=value;如果子進程應該繼承當前進程的環(huán)境,則該參數(shù)為null。
dir:子進程的工作目錄;如果子進程應該繼承當前進程的工作目錄,則該參數(shù)為null。
cmdarray:包含所調(diào)用命令及其參數(shù)的數(shù)組。
獲取進程信息
- 獲取進程的shell字符串 ps aux | grep procName| grep -v grep
- java 調(diào)用shell 獲取進程信息
/**
* 執(zhí)行shell 獲取進程列表信息
* @param cmd
* @return
*/
private List<ProcessBean> queryProcessByShellCmd(final String cmd) {
List<ProcessBean> processBeanList = new ArrayList<ProcessBean>();
Process process = null;
BufferedReader bufferOutReader = null, buffErrReader = null;
String command = StringUtils.trimToNull(cmd);
if(StringUtils.isEmpty(command)) {
return processBeanList;
}
try {
process = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command});
process.waitFor();
bufferOutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while((line = bufferOutReader.readLine()) != null) {
//解析ps返回的進程信息,組裝成ProcessBean對象
ProcessBean processBean = parserProcessBean(line);
if (processBean == null) {
continue;
}
logger.info("=============>>> 查詢進程返回信息:{},解析進程對象信息:{}", line, processBean);
processBeanList.add(processBean);
}
bufferOutReader.close();
bufferOutReader = null;
buffErrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while((line = buffErrReader.readLine()) != null) {
logger.info("=============>>> 讀取錯誤管道流信息:{}", line);
}
buffErrReader.close();
buffErrReader = null;
process.destroy();
process = null;
} catch (IOException e) {
logger.error("======>>執(zhí)行Shell腳本失敗, e:{}", e);
} catch (InterruptedException e) {
logger.error("======>>執(zhí)行Shell腳本失敗, e:{}", e);
} finally {
try {
if(bufferOutReader != null) {
bufferOutReader.close();
}
if(buffErrReader != null) {
buffErrReader.close();
}
if(process != null) process.destroy();
} catch (Exception e){
logger.error("===========>> {}", e);
}
}
return processBeanList;
}
查詢所需的進程列表信息
/**
* 查詢進程列表
* @return
*/
private List<ProcessBean> queryProcessList() {
List<ProcessBean> processBeanList = new ArrayList<ProcessBean>();
String osname = SystemUtils.osName().toLowerCase();
if(osname.indexOf("window") >= 0) {
return processBeanList;
}
final String iafCmd = "ps aux | grep iaf | grep -v grep";
final String dimpleCmd = "ps aux | grep dimp | grep -v grep";
final String tradeCmd = "ps aux | grep | grep -v grep";
final String TomcatCmd = "ps aux | grep java | grep tomcat | grep -v grep";
List<ProcessBean> iafProcessList = queryProcessByShellCmd(iafCmd);
List<ProcessBean> dimpleProcessList = queryProcessByShellCmd(dimpleCmd);
List<ProcessBean> tradeProcessList = queryProcessByShellCmd(tradeCmd);
List<ProcessBean> tomcatProcessList = queryProcessByShellCmd(tomcatCmd);
processBeanList.addAll(iafProcessList);
processBeanList.addAll(dimpleProcessList);
processBeanList.addAll(tradeProcessList);
processBeanList.addAll(tomcatProcessList);
return processBeanList;
}
重啟業(yè)務進程
為了解決在某個進程啟動失敗的時候,web端可以獲取到該進程的信息, 需要通過shell返回一個int值, 每個進程啟動結(jié)果占用1個bit位方式實現(xiàn),web端獲取結(jié)果后,解決返回的結(jié)果,然后判斷是否有進程啟動失敗。
首先準備好shell腳本,內(nèi)容如下:
#!/bin/bash
fileHome=/home/lehoon/interface
#返回值,默認0
retParam="0"
if [ -n "$1" ] ;then
fileHome=$1
fi
interface_home=$fileHome
#查詢gateway的守護shell是否存在,存在就結(jié)束掉
pid=$(ps -fe | grep run_gateway.sh | grep -v grep | awk '{print $2}')
for x in $pid; do kill -9 $x; done
pkill -9 gateway
#sleep 2s
#echo Stop gateway
#Nginx stop
/usr/local/sbin/nginx -s stop
#sleep 2s
#echo Stop nginx
pkill -9 interface_uapmm
#sleep 2s
#echo Stop interface_uapmm...
cd $interface_home/interface_uapmm/bin
sh $interface_home/interface_uapmm/bin/startup.sh > startlup.log&
#sleep 2s
#echo Start interface_uapmm done.
cd $interface_home/gateway/bin
sh $interface_home/gateway/bin/startup.sh > startup.log&
#sleep 2s
#echo Start gateway done.
cd /usr/local/sbin/
sh /usr/local/sbin/run_nginx.sh >> nginx.log &
#sleep 1s
sleep 1s
OLD_IFS="$IFS"
IFS=" "
#query interface_uapmm program is exits
interface_uapmm_pid=$(ps -fe | grep "./interface_uapmm" | grep -v grep | awk '{print $2}')
interface_uapmm_pid_array=($interface_uapmm_pid)
interface_uapmm_pid_len=${#interface_uapmm_pid_array[@]}
if [ $interface_uapmm_pid_len -eq 1 ]; then
retParam=1
fi
#query gateway program is exits
gateway_shell_pid=$(ps -fe | grep "gateway" | grep -v grep | awk '{print $2}')
gateway_shell_pid_array=($gateway_shell_pid)
gateway_shell_pid_len=${#gateway_shell_pid_array[@]}
if [ $gateway_shell_pid_len -eq 1 ]; then
retParam=$(($retParam + 2))
fi
#query nginx program is exits
nginx_pid=$(ps -fe | grep "nginx" | grep -v grep | awk '{print $2}')
nginx_pid_array=($nginx_pid)
nginx_pid_len=${#nginx_pid_array[@]}
if [ $nginx_pid_len -eq 1 ]; then
retParam=$(($retParam + 4))
fi
IFS="$OLD_IFS"
echo $retParam
shell通過返回一個integer值,java獲取到后,通過判斷結(jié)果就可以知道哪些進程啟動失敗了。
java代碼如下:
/**
* 重啟接口腳本
* @return
*/
@RequestMApping(value = "interface/restart")
@RequiresPermissions(value = {"business:operation:maintenance:interface:restart"})
public String restartInterface(HttpServletRequest request, HttpServletResponse response) throws BusinessException{
String osname = SystemUtils.osName().toLowerCase();
if(osname.indexOf("window") >= 0) {
//增加日志記錄
LogUtils.saveLog(request, "系統(tǒng)運維", "手動重啟接口系統(tǒng)失敗, 不支持當前window系統(tǒng)",
Log.TYPE_ACCESS, UserUtils.getUser().getId());
throw new BusinessException("-1", "運維腳本不支持Window系統(tǒng),重啟接口失敗.");
}
String shellDictName = SHELL_FILE_NAME_MAP.get("interface");
String shellFile = DictUtils.getDictValue(shellDictName, "SYSTEM_MAINTENANCE", "");
shellFile = StringUtils.trimToEmpty(shellFile);
logger.info(String.format("======>>手動重啟接口系統(tǒng),接口啟動shell腳本[%s]", shellFile));
if(StringUtils.isEmpty(shellFile)) {
//增加日志記錄
LogUtils.saveLog(request, "系統(tǒng)運維",
"手動重啟接口系統(tǒng)失敗, 接口啟動shell腳本沒有配置在字典表中",
Log.TYPE_ACCESS, UserUtils.getUser().getId());
logger.info("======>>手動重啟接口系統(tǒng)失敗,接口啟動shell腳本沒有配置在字典表中");
throw new BusinessException("-1", "接口啟動shell腳本沒有配置在字典表中,啟動失敗.");
}
String shellResult = StringUtils.trimToEmpty(runShellFile(shellFile));
logger.info(String.format("======>>執(zhí)行shell腳本[%s],返回值[%s]", shellFile, shellResult));
int shellResultCode = -1;
try {
shellResultCode = Integer.parseInt(shellResult);
} catch (NumberFormatException e) {
logger.error("============>>> 轉(zhuǎn)換shell腳本返回結(jié)果失敗{}", e);
//增加日志記錄
LogUtils.saveLog(request, "系統(tǒng)運維",
String.format("手動重啟接口系統(tǒng)失敗, 轉(zhuǎn)換shell腳本返回結(jié)果失敗,返回結(jié)果%s", shellResult),
Log.TYPE_ACCESS, UserUtils.getUser().getId());
throw new BusinessException("-1", "接口啟動失敗,請檢查shell腳本是否有誤.");
}
if(RESTART_INTERFACE_SUCCESS == shellResultCode) {
//增加日志記錄
LogUtils.saveLog(request, "系統(tǒng)運維","交易接口重啟成功",
Log.TYPE_ACCESS, UserUtils.getUser().getId());
AjaxSuccess success = new AjaxSuccess("交易接口重啟成功");
return renderJson(response, success);
}
StringBuilder sb = new StringBuilder("重啟接口失敗, 未啟動成功的進程包括:");
//查詢錯誤進程信息
//interface_uapmm進程
if((shellResultCode & 0x1) == 0) {
//interface_uapmm出錯
sb.append("結(jié)果共享緩存、");
}
if(((shellResultCode >> 1) & 0x1) == 0) {
//dimphq run_dimp.sh腳本出錯
sb.append("行情啟動Shell進程、");
}
//gateway進程
if(((shellResultCode >> 2) & 0x1) == 0) {
//gateway出錯
sb.append("接口網(wǎng)關(guān)、");
}
if(((shellResultCode >> 3) & 0x1) == 0) {
//nginx腳本出錯
sb.append("nginx、");
}
sb.deleteCharAt(sb.length() - 1);
String message = sb.toString();
logger.info("=====>>>>管理員:{},本次重啟接口進程失敗, {}", UserUtils.getUser().getLoginName(), message);
//增加日志記錄
LogUtils.saveLog(request, "系統(tǒng)運維", message, Log.TYPE_ACCESS, UserUtils.getUser().getId());
throw new BusinessException("-1", message);
}