您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java通过shell脚本监控重启服务

时间:2020-08-12 14:45:46  来源:  作者:

简介

最近运维人员提出需求,增加一个运维页面, 查询当前的业务进程信息包括:进程名称、启动命令、启动时间、运行时间等,可以通过页面点击重启按钮,可以重启后端的一系列系统进程。

思路

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字符串 ps aux | grep procName| grep -v grep
  • java 调用shell 获取进程信息
/**
     * 执行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;
    }
Java通过shell脚本监控重启服务

 

重启业务进程

为了解决在某个进程启动失败的时候,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);
    }


Tags:Java   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  Tags: Java  点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Tags: Java  点击:(5)  评论:(0)  加入收藏
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === &#39;a&#39;) { b = true} else { b = false}// goodb = a === &#39;a&#39;2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Tags: Java  点击:(5)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  Tags: Java  点击:(10)  评论:(0)  加入收藏
传统游戏项目一般使用TCP协议进行通信,得益于它的稳定和可靠,不过在网络不稳定的情况下,会出现丢包严重。不过近期有不少基于UDP的应用层协议,声称对UDP的不可靠进行了改造,这意...【详细内容】
2021-12-23  Tags: Java  点击:(12)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  Tags: Java  点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  Tags: Java  点击:(10)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  Tags: Java  点击:(14)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  Tags: Java  点击:(17)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  Tags: Java  点击:(19)  评论:(0)  加入收藏
▌简易百科推荐
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(12)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(10)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(10)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(14)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(17)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
一、概述观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察...【详细内容】
2021-12-13  唯一浩哥    Tags:Java   点击:(16)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条