标题: [企业应用] 专家教您如何避免重启你的应用程序
问天
元帅
Rank: 1


元帅勋章 终身成就勋章
UID 11493
精华 186
积分 34095
帖子 33340
威望 88
金币 13135
热心 2606
阅读权限 100
注册 2006-4-7
状态 离线
专家教您如何避免重启你的应用程序

在开发测试阶段,某个功能模块出错或者功能需求改变,这时候程序员通常会修改源代码,然后重新编译,停止应用程序,重起应用程序。然后检测修改得功能是否正确,是否满足需求。很好,这一切在开发测试阶段都没有问题,无可厚非。不过到了应用正式上线就出现麻烦了。

重启应用会导致系统不可用,或者导致用户请求、响应丢失。甚至有的系统本生就要求为系统动态添加功能,在没有为你的应用添加防止重启动的策略前,通常能做的是到凌晨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演示如何避免重启动应用程序

QUOTE:
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);
        //加载类
  }

}

如下调用:

QUOTE:
DyLoader loader = new DyLoader();
Class a  = loader.loadFromCustomRepository
("com.lijz.SampleDyService");
Class b = loader.loadFromCustomRepository
("com.lijz.SampleDyService");

第三行代码将会抛出:

QUOTE:
java.lang.LinkageError:
duplicate class definition:
com/lijz/SampleDyService

如果如下调用,则一切正常,这是因为你使用新的ClassLoader实例来装载com.lijz.SampleDyService"

QUOTE:
DyLoader loader= new DyLoader();
Class a loader.loadFromCustomRepository
("com.lijz.SampleDyService");
DyLoader newLoader = new DyLoader();
Class b = newLoader.loadFromCustomRepository
("com.lijz.SampleDyService");


网友 问天 签名 - 网友社区 请您回个帖。谢谢
PR查询 免费域名 免费空间
顶部
[广告] 免费域名(Free Subdomain) 免费空间(Free hosting) PR查询(Google Pagerank)
问天
元帅
Rank: 1


元帅勋章 终身成就勋章
UID 11493
精华 186
积分 34095
帖子 33340
威望 88
金币 13135
热心 2606
阅读权限 100
注册 2006-4-7
状态 离线
言归正传,停止介绍Classloader,回到利用Classloader来避免重新启动你的应用程序首先定义业务逻辑处理模块接口:

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

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

一个实现的例子如下:

QUOTE:
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方法:

QUOTE:
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已经覆盖了原来的类。在不启动应用程序前提条件下如何更新新的业务逻辑呢?

分2步来完成:

第一步,在Main类里添加notifyReLoad方法,用新的classloader重新生成SampleDyService实例:

QUOTE:
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应用中,提供这样的界面调用。在此例子中提供一个检测线程。

QUOTE:
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类的构造函数成如下

QUOTE:
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。控制台输出信息如下:

QUOTE:
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能帮助你动态加载类,从而在不停止应用程序的情况下动态更新类。

网友 问天 签名 - 网友社区 请您回个帖。谢谢
PR查询 免费域名 免费空间
顶部



当前时区 GMT+8, 现在时间是 2008-8-22 12:20
信产部ICP备案:京ICP备05066424号 北京市公安局网监备案:1101050648号

Powered by Discuz! 5.5.0
清除 Cookies - 联系我们 - 网友俱乐部 - Archiver - WAP