最近运维人员提出需求,增加一个运维页面, 查询当前的业务进程信息包括:进程名称、启动命令、启动时间、运行时间等,可以通过页面点击重启按钮,可以重启后端的一系列系统进程。
JAVA程序获取linux进程信息可以通过shell脚本获取进程信息、通过读取proc文件系统获取进程信息。 但是为了系统的安全性、方便维护等角度出发,更多的是java通过shell获取和linux交互能力。
java程序中要执行linux命令主要依赖2个类:Process和Runtime:
Process:
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例, 该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、 检查进程的退出状态以及销毁(杀掉)进程的方法。 创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr) 操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。 父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小, 如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。 当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。 对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。
1,创建的子进程没有自己的终端控制台,所有标注操作都会通过三个流
(getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程(父进程可通过这些流判断子进程的执行情况)
2,因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,
则可能导致子进程阻塞,甚至产生死锁
特别需要注意:如果子进程中的输入流,输出流或错误流中的内容比较多,最好使用缓存(注意上面的情况2)
Runtime
每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时环境。 应用程序不能创建自己的Runtime类实例。
Process exec(String command)
在单独的进程中执行指定的字符串命令。
Process exec(String command, String[] envp)
在指定环境的单独进程中执行指定的字符串命令。
Process exec(String command, String[] envp, File dir)
在有指定环境和工作目录的独立进程中执行指定的字符串命令。
Process exec(String[] cmdarray)
在单独的进程中执行指定命令和变量。
Process exec(String[] cmdarray, String[] envp)
在指定环境的独立进程中执行指定命令和变量。
Process exec(String[] cmdarray, String[] envp, File dir)
在指定环境和工作目录的独立进程中执行指定的命令和变量。
command:一条指定的系统命令。
envp:环境变量字符串数组,其中每个环境变量的设置格式为name=value;如果子进程应该继承当前进程的环境,则该参数为null。
dir:子进程的工作目录;如果子进程应该继承当前进程的工作目录,则该参数为null。
cmdarray:包含所调用命令及其参数的数组。
/**
* 执行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("======>>执行Shell脚本失败, e:{}", e);
} catch (InterruptedException e) {
logger.error("======>>执行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;
}
为了解决在某个进程启动失败的时候,web端可以获取到该进程的信息, 需要通过shell返回一个int值, 每个进程启动结果占用1个bit位方式实现,web端获取结果后,解决返回的结果,然后判断是否有进程启动失败。
首先准备好shell脚本,内容如下:
#!/bin/bash
fileHome=/home/lehoon/interface
#返回值,默认0
retParam="0"
if [ -n "$1" ] ;then
fileHome=$1
fi
interface_home=$fileHome
#查询gateway的守护shell是否存在,存在就结束掉
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获取到后,通过判断结果就可以知道哪些进程启动失败了。
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, "系统运维", "手动重启接口系统失败, 不支持当前window系统",
Log.TYPE_ACCESS, UserUtils.getUser().getId());
throw new BusinessException("-1", "运维脚本不支持Window系统,重启接口失败.");
}
String shellDictName = SHELL_FILE_NAME_MAP.get("interface");
String shellFile = DictUtils.getDictValue(shellDictName, "SYSTEM_MAINTENANCE", "");
shellFile = StringUtils.trimToEmpty(shellFile);
logger.info(String.format("======>>手动重启接口系统,接口启动shell脚本[%s]", shellFile));
if(StringUtils.isEmpty(shellFile)) {
//增加日志记录
LogUtils.saveLog(request, "系统运维",
"手动重启接口系统失败, 接口启动shell脚本没有配置在字典表中",
Log.TYPE_ACCESS, UserUtils.getUser().getId());
logger.info("======>>手动重启接口系统失败,接口启动shell脚本没有配置在字典表中");
throw new BusinessException("-1", "接口启动shell脚本没有配置在字典表中,启动失败.");
}
String shellResult = StringUtils.trimToEmpty(runShellFile(shellFile));
logger.info(String.format("======>>执行shell脚本[%s],返回值[%s]", shellFile, shellResult));
int shellResultCode = -1;
try {
shellResultCode = Integer.parseInt(shellResult);
} catch (NumberFormatException e) {
logger.error("============>>> 转换shell脚本返回结果失败{}", e);
//增加日志记录
LogUtils.saveLog(request, "系统运维",
String.format("手动重启接口系统失败, 转换shell脚本返回结果失败,返回结果%s", shellResult),
Log.TYPE_ACCESS, UserUtils.getUser().getId());
throw new BusinessException("-1", "接口启动失败,请检查shell脚本是否有误.");
}
if(RESTART_INTERFACE_SUCCESS == shellResultCode) {
//增加日志记录
LogUtils.saveLog(request, "系统运维","交易接口重启成功",
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("结果共享缓存、");
}
if(((shellResultCode >> 1) & 0x1) == 0) {
//dimphq run_dimp.sh脚本出错
sb.append("行情启动Shell进程、");
}
//gateway进程
if(((shellResultCode >> 2) & 0x1) == 0) {
//gateway出错
sb.append("接口网关、");
}
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, "系统运维", message, Log.TYPE_ACCESS, UserUtils.getUser().getId());
throw new BusinessException("-1", message);
}