统一对象消息编程详解——热部署与动态配置

       曾经用php编写代码没有动态配置的概念,代码修改完后直接刷新页面就可以看到效果,不用重新启动server程序。在学习java和开发消息框架的过程中,有一件事情让我觉得特别麻烦,每次更改模块配置后就要重新启动tomcat,这时我才有动态配置的概念——不用重新启动程序让模块配置能立即生效。动态配置不仅在开发、调试过程中需要,节省时间精力,而且在程序上线运行中也重要,我们能及时的更改运行配置而反应业务需要,例如一个url由映射到A模块改为B模块,如果需要停止web服务,那太糟糕了。

   我这里区分热部署和动态配置为两个概念,热部署为运行中模块或者对象类的更换,动态配置为当对象配置改变时,配置能立即、尽快的生效。传统上热部署由虚拟机的类加载器来实现,通过开发自己的类加载器来检测对象类的变化来重新加载。我觉得这一来是麻烦,而来是缺乏灵活。我们来看对于消息编程框架如何实现的。

      当模块都实例化后,模块是无法知道配置文件改变的,单对每个模块开发检测配置文件功能将繁琐而麻烦,况且配置的改变是根据业务情况需要的,也许后来业务不需要运行中改变。动态配置不应该是业务模块的功能,属于维护方便的辅助。用传统的对象编程实现这个功能比较困难,因为每个对象都是不同的,但在我的消息对象框架中,所有的模块或对象都基于一个公共模板类——TLbaseModule,那么这将很容易的实现。

    首先在公共模板类TLbaseModule中增加配置重新载入功能:

            case RELOADCONFIG:
                setConfig();
                initProperty();
                reload();
                putLog(name+" 重新加载配置",LogLevel.INFO);

 上面的代码片段意思是 收到消息 RELOADCONFIG时,重新加载配置。

  模块有重新载入配置的功能了,那么下一个问题是模块怎么得到这个消息。我的思路是由单独的线程运行配置文件监听模块,由监听模块监听配置文件,当某个模块的配置文件发生改变时给这个模块发送RELOADCONFIG消息。监听模块代码:

public class TLMonitorConfigModule extends TLBaseModule {
    protected  TLMsg reloadMsg=createMsg().setAction("reloadConfig");
    protected Map<String,String> moduleConfigFiles;
    protected Map<String,Long> fileLasttime;
    protected boolean ifStop =false;
    public TLMonitorConfigModule(){
        super();
    }
    public TLMonitorConfigModule(String name ){
        super(name);
    }
    public TLMonitorConfigModule(String name , TLObjectFactory modulefactory){
        super(name,modulefactory);
    }


    protected void initProperty(){
        super.initProperty();
        initModulesMap();
    }
    protected void initModulesMap(){
        moduleConfigFiles=new ConcurrentHashMap<>();
        fileLasttime=new ConcurrentHashMap<>();
        moduleConfigFiles.put(name,configFile);
        fileLasttime.put(name, Long.valueOf(0));
        String modules[] = params.get("modules").split(";");
        for(int i=0;i<modules.length;i++){
                modules[i]=modules[i].trim();
                moduleConfigFiles.put(modules[i],"");
                fileLasttime.put(modules[i], Long.valueOf(0));
        }
    }

    @Override
    protected void init() {
        putMsg(this,createMsg().setAction("checkModules")
                .setParam(SESSIONDEAMON,true)
                .setParam(EXCEPTIONHANDLER,new MyUnchecckedExceptionhandler(this,createMsg().setAction("restart")))
                .setWaitFlag(false));
        putLog("监听配置文件模块启动",LogLevel.INFO);
    }
    @Override
    protected TLMsg checkMsgAction(Object fromWho, TLMsg msg) {
        switch (msg.getAction()) {
            case"checkModules":
                ifStop=false;
                checkTask();
                break;
            case"stop":
                ifStop=true;
                break;
            case"restart":
                restart(fromWho,msg);
                break;
            default:
                ;
        }
        return null;
    }

    private void restart(Object fromWho, TLMsg msg) {
        Throwable e = (Throwable) msg.getParam("throwable");
        putLog("发生异常,监听配置文件模块重启",LogLevel.ERROR);
        putLog(e.getMessage() +e.getCause(),LogLevel.ERROR);
        init();
    }

    protected void checkTask() {
        int delay=10;
        if(params.get("delay")!=null)
            delay= Integer.parseInt(params.get("delay"));
        int i =0 ;
        while (!ifStop){
            try {
                i++;
                Thread.sleep(delay*1000);
                if(i >=10)
                {
                    putLog("监听模块工作中。。。。",LogLevel.INFO);
                    i=0;
                }
                checkModules();
            } catch (InterruptedException e) {
        //        e.printStackTrace();
                putLog("发生异常,监听配置文件模块重启",LogLevel.INFO);
                init();
            }
        }
    }
    protected void checkModules() {
     for (String moduleName:moduleConfigFiles.keySet())
     {
       if(ifStop==true)
           return;
        String configFile=moduleConfigFiles.get(moduleName);
        if(configFile==null)
            continue;
        if(configFile.equals(""))
        {
           TLBaseModule module= (TLBaseModule) getModuleInFactory(moduleName);
           if(module!=null)
           {
               configFile=module.getConfigFile();
               if(configFile==null)
                   continue;
           }
           else
               continue;
        }

        Long lasttime=fileLasttime.get(moduleName);
        if(lasttime==null)
            continue;
        if(lasttime==0){
            File file = new File(configFile);
            long nowTime = file.lastModified();
            fileLasttime.put(moduleName,nowTime);
            continue;
        }
         Long nowTime =checkFileTime(configFile,lasttime);
         if(nowTime >0)
          {
              fileLasttime.put(moduleName,nowTime);
              putMsg(moduleName,reloadMsg);
              putLog(moduleName+"重新配置加载",LogLevel.WARN);
          }
     }
    }
    protected Long checkFileTime(String moduleConfigFile, Long lastTime) {
        File file = new File(moduleConfigFile);
        long newTime = file.lastModified();
        if(newTime >lastTime)
            return newTime;
        else
            return Long.valueOf(0);
    }

    public class MyUnchecckedExceptionhandler implements Thread.UncaughtExceptionHandler {
        private IObject module;
        private  TLMsg msg;

         public  MyUnchecckedExceptionhandler(IObject module, TLMsg msg){
             this.module =module;
             this.msg =msg ;
         }
        @Override
        public void uncaughtException(Thread t, Throwable e) {
          module.putMsg(module,msg.setParam("throwable" ,e));
        }
    }
}

   模块实例化后在init()方法中启动线程。通过给自身发送启动消息,消息参数为异步(线程方式),模块自动运行,开始检测配置文件,设定每十秒循环一次。当某个模块的配置文件发送改变(根据文件修改时间)时,给这个模块发送重新加载配置文件消息。下面看监听模块的配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<moduleConfig>
    <params>
        <delay value="4" />
        <modules value="moduleFactory;appCenter;cache;website;qqdbserver;abMsgtable;infomationControl;
        log;urlMap;velocity;servletCache;clientJsonToMsg;webauth;webUser;memberControl;userInPutFilter;servletCache;informationModule"   />
      
    </params>
</moduleConfig>

   modules项中定义监听被监听模块 ,由于监听模块也监听自身,因此随时可加入、取消被监听模块,配置方便灵活。当检测被监听模块的配置文件发生改变,给监听模块发送重新加载配置消息,完成动态配置。     

      监听模块如何启动,在工厂启动项中增加一个模块启动即可,如果取消功能,从配置中删掉即可。

  <factoryBoot>           
            <msg action="getModule"  moduleName="monitorConfig" usePreReturnMsg="false" />           
  </factoryBoot>

       上面为在工厂启动中,增加启动消息来启动监听模块。监听模块启动后自动开启新的线程,而不影响主线程,因此对其他模块透明。

     下图为一个动态更新例子。程序运行中为方便查看日志,需要动态对模块进行日志输出,比如开启或停止哪个模块的日志,图中更改了日志模块的配置文件log_config.xml ,立即被监听模块检查到而重载入配置。

 

       现在我们来看热部署----动态更新模块类。既然能动态更新配置了,那么动态更新模块自然实现了。在消息编程框架中,所有的模块由模块工厂统一创建,那么思路就是重新由工厂创建一个就行了 。我的消息框架里模块之间互动不是通过对象实例,而是对象名称。一个名称对应哪个具体类不重要,重要是实现功能就行了 。下面为模块工厂配置文件的模块配置项:

  <modules>
        <!-- 系统模块,不要去掉!以符号.开始的类在默认包下 -->
        <module name="broadcast" classfile="cn.tianlong.tlobjcet.base.TLMsgBroadCast" />
        <module name="log" classfile="cn.tianlong.tlobjcet.base.TLLog"
                configfile="log_config.xml"/>
        <module name="monitorConfig" classfile=".servletutils.TLMonitorConfigForThread"
                configfile="monitorConfig_config.xml"/>
        <module name="msgbus"  classfile="cn.tianlong.tlobjcet.base.TLMsgBus" />

   对于 log 模块 ,如其他模块调用log模块,(方法:putmsg(“log”,msg)),调用是用的模块名称“log”,现在这个log名字在工厂中对应的是TLLog类,如果想更换类则更改工厂配置。比如更改为 TLNewLog,则“log” 指向新的类,对其他模块不影响,因为其他模块使用的是其名字。那么实现热部署可由以下两步:

第一步:更改模块工厂里面的配置 ,将log 指向新类 TLNewLog     

<module name="log" classfile="cn.tianlong.tlobjcet.base.TLNewLog"
                configfile="log_config.xml"/>

   由于动态配置,模块工厂读取了新的配置。但这时候运行中的模块实例还没有变,log名字还是指向的TLlog实例。我们必须将新类TLNewLog实例化代替过去的实例。这通过下面第二步完成。

第二步:更改模块配置文件 log_confif.xml 刷新实例。

          对于配置文件param项参数 ,设置<reload value="true" />,参数意思是,模块重新实例化。

       由于配置文件更改,模块开始刷新配置,刷新配置代码reload()方法中:

 private  void reload()
    {
        if(params != null && params.get("reload")!=null && (params.get("reload")).equals("true"))
            putMsg(moduleFactory,createMsg().setAction("reloadModule").setParam("moduleName",name))  ;
    }

   模块给模块工厂发送reloadModule的消息重新加载模块,在模块工厂中消息对应的方法:

  private void reloadModule(Object fromWho, TLMsg msg) {
        String moduleName= (String) msg.getParam("moduleName");
        if(moduleName==null)
            moduleName=((IObject)fromWho).getName();
        Object module = createModule( moduleName,moduleName);
        if (module != null)
        {
            modules.put(moduleName, module);
            putToSession(moduleName,module);
            TLMsg  resetmsg =createMsg().setAction(RESETMODULE).setParam("moduleName",moduleName)
                       .setParam("module",module);
            for (Map.Entry<String, Object> entry : modules.entrySet()) {
                if(entry.getKey().equals(name) || entry.getKey().equals(SESSIONDATAMODULE))
                    continue;
                Object moduleObj =  entry.getValue();
                if(moduleObj instanceof TLBaseModule)
                    putMsg((IObject) moduleObj,resetmsg);
            }
            putLog(moduleName+"  模块重新加载",LogLevel.WARN);
        }
    }

     上面代码中,模块工厂重新创建了新模块。由于其他模块还保留着原来模块的实例(为方便,每个模块保存它用过模块的实例),因此要给其他模块发出模块更新的通知消息。对于其他模块来说,这一切都是透明的,还是一样的使用“log“”,但是log指向的对象实例已经变了。这样我们没有增加复杂的代码,通过动态配置与工厂统一创建的功能,将模块更换了 。

   对于热部署增加新的模块,因为还没有实例化,无需刷新对象实例,则更加简单,在工厂配置中增加一个module项即可,动态配置生效后就可使用。

 

 

 

   

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值