Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 375|回复: 0

[Java基础知识]避免重启你的应用程序

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-1 14:00:36 | 显示全部楼层 |阅读模式
    在开发测试阶段,某个功能模块出错或者功能需求改变,这时候程序员通常会修改源代码,然后重新编译,停止应用程序,重起应用程序。然后检测修改得功能是否正确,是否满足需求。很好,这一切在开发测试阶段都没有问题,无可厚非。不过到了应用正式上线就出现麻烦了。重启应用会导致系统不可用,或者导致用户请求、响应丢失。甚至有的系统本生就要求为系统动态添加功能,在没有为你的应用添加防止重启动的策略前,通常能做的是到凌晨2点趁用户少的时候重启你的应用或者是暂时切换到备份系统。

            如果你的系统也遇到过或者将不可避免的遇到这样的问题,那在这篇文章里,几个解决办法可以供你选择使用。

             一:在启动前,保存未被处理的请求和未发给用户的响应。较好的方式是将接收请求,发送响应的模块与你处理应用逻辑的模块分开设计。如现在的web服务器,可以在你重新部署(redploy)的时候暂把用户的请求保存到队列,等到部署成功后在提交给处理逻辑的模块。又如,接收请求,发送响应的模块是不同的应用。与应用逻辑模块之间通过文件(把请求序列化成一个文件)等方式交换数据,这样在你的逻辑应用重启后,仍然可以继续读取请求

      
      
      
       
      
            二:如果更新的功能是纯数据的,那么,采用动态配置,避免重启动。比如,一个web系统,提供reloadConfig界面,重新从配置文件里读取数据。一个java应用程序,你可以在你的应用程序里启动一个检测线程,检测文件是否被改变,如果改变,则自动重新转载配置。如下例子:

       public ConfigChecker extends Thread
       {
           SystemManager sm = null;
           long time ;
           public ConfigChecker(SystemManager sm) throws ApplicationException
           {
               this.sm = sm;
               time = getConfigFileTime();
           }
           pulblic void run()
           {
               
                while(!interrupted())
                {
                        try
                   {
                       long newTime  = getConfigFileTime();
                       if(newTime!=time)
                       {
                           time = newTime;
                           //重新装载配置
                           sm.reloadConfig();
                      
                       }
                       Thread.sleep(1000*3)
                   }
                   catch(Exception ex)
                   {
                       return ;
                   }
                        
                }
           }
          
           public long getConfigFileTime() throws ApplicationException
           {
               try
               {
                   File f = new File(sm.getConfigFile());
                   return f.lastModified()
               }
               catch(IOException ex)
               {
                   throw new ApplicationException(ex.getMessage())
               }
               
           }
       }
       
       
       这种方式适合你要动态改变的是纯数据,它要求你应用中的数据不能写死在代码里,而是通过文件配置。通过重新从配置文件里读取数据避免重启动
       
       三:如果更新的功能包括应用逻辑,也就是class改变了,那就稍微麻烦点,你需要了解ClassLoader的原理。使用你定制的ClassLoader重新Load 已经编译好的class,就好比你重启应用一样。下面将简单介绍ClassLoader原理,以及举出一个例子来说明如何避免重启应用程序
       
           虚拟机通过Classloader来装载类。bootstrap loader 负责load jdk的class(java.*,javax.*), system class loader是bootstrap的子类,负责load 所有在chasspath指定的变量。ClassLoader将字节流转化为Class类,这些字节流可能来源文件,也可能来源于网络或者数据库。转化方法是调用ClassLoader提供的final defineClass(className, classBytes, 0, classBytes.length)方法来实现。需要记住的是虚拟机里一个类的唯一标识是通过类的包名+类名+装载此类的ClassLoader。同一个ClassLoader实例只能装载Class一次,重复装载将抛出重复类定义异常。
           如下自定义ClassLoader将从classpath里装载指定的类,来说明如上对ClassLoader的介绍,同时,我们用此ClassLoader演示如何避免重启动应用程序
          
    public class DyLoader extends ClassLoader
    {
         public DyLoader()
         {
             super(DyLoader.class.getClassLoader());
         }

         public Class loadFromCustomRepository(String className) {

         /**取环境变量*/
         String classPath = System.getProperty("java.class.path");
         List classRepository = new ArrayList();

         /**取得该路径下的所有文件夹 */
         if ( (classPath != null) && ! (classPath.equals(""))) {
           StringTokenizer tokenizer = new StringTokenizer(classPath,  File.pathSeparator);
           while (tokenizer.hasMoreTokens()) {
             classRepository.add(tokenizer.nextToken());
           }
         }
         Iterator dirs = classRepository.iterator();
         byte[] classBytes = null;

         /**在类路径上查找该名称的类是否存在,如果不存在继续查找*/
         while (dirs.hasNext()) {
           String dir = (String) dirs.next();
           //replace "." in the class name with File.separatorChar & append .class to the name
           String classFileName = className.replace(".", File.separatorChar);
           classFileName += ".class";
           try {
             File file = new File(dir + File.separatorChar + classFileName);
             if (file.exists()) {
               InputStream is = new FileInputStream(file);
               /**把文件读到字节文件*/
               classBytes = new byte[is.available()];
               is.read(classBytes);
               break;
             }
           }
           catch (IOException ex) {
             System.out.println("IOException raised while reading class file data");
             ex.printStackTrace();
             return null;
           }
         }
         return this.defineClass(className, classBytes, 0, classBytes.length);//加载类
       }

    }

    如下调用
    DyLoader loader = new DyLoader();
    Class a  = loader.loadFromCustomRepository("com.lijz.SampleDyService");
    Class b = loader.loadFromCustomRepository("com.lijz.SampleDyService");
    第三行代码将会抛出
    java.lang.LinkageError: duplicate class definition: com/lijz/SampleDyService



    如果如下调用,则一切正常,这是因为你使用新的ClassLoader实例来装载com.lijz.SampleDyService"
    DyLoader loader= new DyLoader();
    Class a loader.loadFromCustomRepository("com.lijz.SampleDyService");
    DyLoader newLoader = new DyLoader();
    Class b = newLoader.loadFromCustomRepository("com.lijz.SampleDyService");

    言归正传,停止介绍Classloader,回到利用Classloader来避免重新启动你的应用程序

    首先定义业务逻辑处理模块接口

    public interface IDyService
    {
        public void start();
        public void close();
        public void doBusiness();
    }

    start方法用于初始化,close用于关闭此服务。doBusiness用来模拟处理业务

    一个实现的例子如下:
    public class SampleDyService implements IDyService
    {
         public SampleDyService()
         {
         }
         public void doBusiness()
         {
             System.out.println("hello boy");
         }
         public void start()
         {
             System.out.println("Start SampleDyService:");
             System.out.println(SampleDyService.class.getClassLoader());
         }

         public void close()
         {
             System.out.println("close SampleDyService:");
         }


    start方法 close方法仅打印出提示信息。doBuinsess输出"hello boy"。主程序将循环调用doBusiness方法

    public class Main()
    {     
          private IDyService service = null;
          public Main()
                 throws Exception
         {
             DyLoader loader = new DyLoader();
             service = (IDyService) loader.loadFromCustomRepository(
                     "com.gctech.service.test.dyloader.SampleDyService").newInstance();
             service.start();
             
             while (true)
             {

                 service.doBusiness();
                 Thread.sleep(1000 * 3);
             }
         }
         
         public static void main(String[] args)
                 throws Exception
         {
             Main main = new Main();
         }
         
    }   
       
       
       假设业务逻辑改变,要求SampleDyService的doBusiness打印出"hello girl"。新编译好的SampleDyService已经覆盖了原来的类。在不启动应用程序前提条件下如何更新新的业务逻辑呢?
       分两步来完成
       第一步,在Main类里添加notifyReLoad方法,用新的classloader重新生成SampleDyService实例
       public void notifyReLoad()
                 throws Exception
         {
             service.close();
             DyLoader loader = new DyLoader();
             service = (IDyService) loader.loadFromCustomRepository(
                     "com.gctech.service.test.dyloader.SampleDyService").newInstance();
             service.start();

         }
       
       第二步:使用某种机制检测来检测SampleDyService.class已经改变,如可以通过上面的例子启动一个线程检测SampleDyService.class是否被改变,如果改变则调用Main.notifyReLoad().也可以采用主动通知方式,如web应用中,提供这样的界面调用。在此例子中提供一个检测线程。
       public class DyServciceChecker extends Thread
    {
         Main main = null;
         
         public DyServciceChecker()
         {
         }
         public void setMain(Main main)
         {
             this.main = main;

         }

         public void run()
         {
             while(!interrupted())
             {
                 try
                 {
                     boolean isChanged = check();
                     if(isChanged)
                     {
                         main.notifyReLoad();

                             }
                     else
                     {
                         Thread.sleep(1000*50);


                     }
                 }
                 catch (Exception ex)
                 {
                     ex.printStackTrace();
                 }
             }
         }
            
    }

    修改Main类的构造函数成如下

      public Main()
                 throws Exception
         {
             DyLoader loader = new DyLoader();
             service = (IDyService) loader.loadFromCustomRepository(
                     "com.gctech.service.test.dyloader.SampleDyService").newInstance();
             service.start();

             //添加检测线程
             DyServciceChecker checker = new DyServciceChecker();
             checker.setMain(this);        
             checker.start();      
             
             while (true)
             {

                 service.doBusiness();
                 Thread.sleep(1000 * 3);
             }
         }
         
    好了,运行Main类。并在运行过程中用新的SampleDyService.class覆盖旧的SampleDyService.class。控制台输出信息如下:


    Start SampleDyService:

    com.gctech.service.test.dyloader.DyLoader@108786b

    hello boy

    hello boy

    hello boy

    ...............
    close SampleDyService:

    Start SampleDyService:

    com.gctech.service.test.dyloader.DyLoader@c1cd1f

    hello girl

    hello girl



    总结:
       如果应用程序不可避免的在运行中要重启动,你首先要做好的工作是采取合理的设计,保证用户的请求和对用户的相应不丢失。否则,只能等到用户访问量最少的时候去启动。还有你在写你的业务逻辑的时候,即使当时你很肯定你的代码写死也没关系也不要这么做,尽量采用配置文件的方法,并提供如上的检测线程,现在的web container都提供检测web.xml文件是否改变来确定是否需要重新部署web。最后Classloader能帮助你动态加载类,从而在不停止应用程序的情况下动态更新类
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-1 12:51 , Processed in 0.380435 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表