zoukankan      html  css  js  c++  java
  • java:如何让程序按要求自行重启?

    正文开始前的废话:
    这里的程序即包括b/s的web application,也包括standalone的类c/s的java application。
     
    为什么要自我重启?
     
    场景1:分布式环境中,一般会有很多应用(即包括c/s的java application,又有b/s的web application)部署在不同的环境中,为了管理方便,通常会把一些公用的配置,比如:报警发邮件用的邮箱账号/密码/smtp信息,公用的ftp账号信息,甚至jdbc的连接串信息等,统一放在某个位置(共享的网络存储目录、redis缓存、database、zookeeper、远程service中均可),这样管理起来比较方便。象邮箱账号这些信息还好,各应用订阅配置的变化,发现变化时,如果是spring环境,直接调用applicationContext.refresh(),配置就会重新刷新。但是对于数据源这种特殊配置,就比较难弄了,要考虑连接池中已经连接成功的connection对象,已经通过旧的datasource查出来的数据,跟旧datasource关联的sqlSesstionFactory,Mapper实例等等,要全部换血,很难保证,最好的办法就是让程序重启。
     
    场景2:写程序嘛,有隐藏的bug在所难免,绝对零bug的程序还是很罕见的,如果随着程序运行时间的不断增加,程序性能越来越差或假死,需要重启一下,通常需要远程连撞到linux,敲命令kill进程,再重启java application,这对于不熟悉linux的新手管理人员,一来可能比较陌生,二来未必有执行权限,所以通过一个友好的监控管理界面,点击下重启按钮,让指定的程序重启,会更容易让人接受。
     
    正文开始:
     
    一、程序如何知道自己需要重启?
    显然,如果有一个程序,用户想正常关闭的时候,程序又自动重启,如此循环,这就成关不掉的恶意程序了。 
    所以,程序应该由单独的进程监听并接收特定的指令,而不影响用户正常关闭程序,思路: 
    程序启动时,生成一个唯一的uuid(或其它标识,只要保证全局唯一就行),然后向zookeeper注册一个临时节点。 
    比如:
    /app/uuid-1
    这样监控中心,只要扫描/app下有多少临时节点,就知道当前运行了哪些应用。 
    管理员从监控中心希望将某个应用重启时,可以向zookeeper写一个节点
    /command/uuid-1 节点的内容,约定为:restart 
    应用启动后,监听/command/uuid-1 节点的数据变化,一旦发现有restart的数据内容,即认为收到了重启指令,然后就按下面的处理,自我毁灭,重新投胎转世。
     
     
    二、java application的重启 
    网上的样例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Runtime.getRuntime().addShutdownHook(new Thread() {
        public void run() {
            try {
                String restartCmd = "nohup java -jar xxx.jar";
                Thread.sleep(10000);//等10秒,保证分身启动完成后,再关掉自己
                logger.debug("程序重启完成!");
            catch (Exception e) {
                logger.error("重启失败,原因:", e);
            }
        }
    });
    logger.debug("程序准备重启!");
    System.exit(0);
    可以改进的地方: 
    a) sleep(10000) 即等待10秒,等自己的『分身』启动好以后,再把自己的『真身』给杀死。这里的10秒,其实也是拍脑袋定的,如果追求完美的话,理论上讲,只要系统进程中出现了新启动的『分身』,就可以将『真身』人道毁灭了。
     
    问题:如果知道『分身』已经启动完成?
    答案:java可以获取 jps -l 的输出,知道当前所有的java进程,这样就可以知道指定的应用有没有启动。可以在重启前,获取一次jps -l 的输出,重启后,再执行一次jps -l 的输出,对比二次输出,如果发现多出一个新的指定进程名,就表示『分身』启动完成,可以结束自己。
     
    附:java代码获取jps输出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import org.apache.logging.log4j.*;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
     
    public final class RuntimeUtil {
     
        private static Logger logger = LogManager.getLogger();
     
        public static String exec(String command) {
            StringBuilder sb = new StringBuilder();
            try {
                Process process = Runtime.getRuntime().exec(command);
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                process.getOutputStream().close();
                reader.close();
                process.destroy();
            catch (Exception e) {
                logger.error("执行外部命令错误,命令行:" + command, e);
            }
            return sb.toString();
        }
     
        public static String jps() {
            return exec("jps -l");
        }
    }

      

    b)  java -jar xxx.jar 这里也不太好,一是xxx.jar是字符串,编译期发现不了错误,二是路径是相对路径,就算启动成功了,最终jps显示的进程名是jar,看不出实际对应的是啥程序(详情可参考 设置 java -jar 的进程显示名称 ),可以代码获取当前程序的实际路径
    1
    2
    3
    4
    5
    6
    7
    public static String getJarExecPath(Class clazz) {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
        if (OSUtil.getOSname().equals(OSType.Windows)) {
            return path.substring(1);
        }
        return path;
    }

    OSUtil代码从这里获取

      

    三、 web application的重启
    这里只讨论部署在jboss上的解决方案,
    这二篇文章中,已经给出了用编码或shell命令来控制jboss的方法,所以web application的按需重启思路就有了:
    从监控界面点击『重启』某个web application时,后台代码先将该web application disable掉,然后再重新enable或assign
  • 相关阅读:
    Java的反射机制
    并发编程--锁--悲观锁和乐观锁
    SpringCloud --服务调用Feign
    微服务
    项目中处理数据常用Excel公式
    接口参数选择
    你真会看idea中的Log吗?
    MySQL--索引
    Redis介绍
    MySQL--SQL执行顺序,Explain
  • 原文地址:https://www.cnblogs.com/tuojunjie/p/7390855.html
Copyright © 2011-2022 走看看