Java类中热替换的概念、设计与实现(4)

    在线升级系统的设计原则

    在上小节中,我们给出了一个Java类热替换的实例,掌握了这项技术,就具备了实现在线升级系统的基础。但是,对于一个真正的产品系统来说,升级本省就是一项非常复杂的工程,如果要在线升级,就会更加复杂。其中,实现类的热替换只是最后一步操作,在线升级的要求会对系统的整体设计带来深远的影响。下面我们来谈谈在线升级系统设计方面的一些原则:

    ◆在系统设计一开始,就要考虑系统的哪些部分是需要以后在线升级的,哪些部分是稳定的

    虽然我们可以把系统设计成任何一部分都是可以在线升级的,但是其成本是非常高昂的,也没有必要。因此,明确地界定出系统以后需要在线升级的部分是明智之举。这些部分常常是系统业务逻辑规则、算法等等。

    ◆设计出规范一致的系统状态转换方法

    替换一个类仅仅是在线升级系统所要做的工作中的一个步骤,为了使系统能够在升级后正常运行,就必须保持升级前后系统状态的一致性。因此,在设计时要考虑需要在线升级的部分所涉及的系统状态有哪些,把这些状态设计成便于获取、设置和转换的,并用一致的方式来进行。

    ◆明确出系统的升级控制协议

    这个原则是关于系统在线升级的时机和流程控制的,不考虑系统的当前运行状态就贸然进行升级是一项非常危险的活动。因此在系统设计中,就要考虑并预留出系统在线升级的控制点,并定义清晰、明确的升级协议来协调、控制多个升级实体的升级次序,以确保系统在升级的任何时刻都处在一个确定的状态下。

    ◆考虑到升级失败时的回退机制

    即使我们做了非常缜密细致的设计,还是难以从根本上保证系统升级一定是成功的,对于大型分布式系统来说尤其如此。因此在系统设计时,要考虑升级失败后的回退机制。

    在线升级系统实例

    首先,我们来简单介绍一下这个实例的结构组成和要完成的工作。在我们的例子中,主要有三个实体,一个是升级控制实体,两个是工作实体,都基于ActiveObject实现,通过命令消息进行通信(关于ActiveObject的详细信息,可以参见作者的另外一篇文章“构建Java并发模型框架”)。

    升级控制实体以RMI的方式对外提供了一个管理命令接口,用以接收外部的在线升级命令。工作实体有两个消息队列,一个用以接收分配给它的任务(我们用定时器定时给它发送任务命令消息),我们称其为任务队列;另一个用于和升级控制实体交互,协作完成升级过程,我们称其为控制队列。工作实体中的任务很简单,就是使用我们前面介绍的Foo类简单地打印出一个字符串,不过这次字符串作为状态保存在工作实体中,动态设置给Foo类的实例的。升级的协议流程如下:

    当升级控制实体接收到来自RMI的在线升级命令时,它会向两个工作实体的任务队列中发送一条准备升级消息,然后等待回应。当工作实体在任务队列中收到准备升级消息时,会立即给升级控制实体发送一条准备就绪消息,然后切换到控制队列等待进一步的升级指令。升级控制实体收齐这两个工作实体发来的准备就绪消息后,就给这两个工作实体的控制队列各发送一条开始升级消息,然后等待结果。工作实体收到开始升级消息后,进行实际的升级工作,也就是我们前面讲述的热替换类。然后,给升级控制实体发送升级完毕消息。升级控制实体收到来自两个工作实体的升级完毕消息后,会给这两个工作实体的控制队列各发送一条继续工作消息,工作实体收到继续工作消息后,切换到任务队列继续工作,升级过程结束。主要的代码片段如下(略去命令消息的定义和执行细节):

    1. // 升级控制实体关键代码 
    2. class UpgradeController extends ActiveObject{  
    3.     int nready  = 0;  
    4.     int nfinished = 0;  
    5.     Worker[] workers;  
    6.     ......  
    7.     // 收到外部升级命令消息时,会触发该方法被调用 
    8.     public void askForUpgrade() {  
    9.         for(int i=0; i<workers.length; i++)  
    10.             workers[i].getTaskQueue().enqueue(new PrepareUpgradeCmd(workers[i])); 
    11.     }  
    12.  
    13.     // 收到工作实体回应的准备就绪命令消息时,会触发该方法被调用 
    14.     public void readyForUpgrade(String worker_name) {  
    15.         nready++;         
    16.         if(nready == workers.length){  
    17.             for(int i=0; i<workers.length; i++)  
    18.                 workers[i].getControlQueue().enqueue(new  
    19.                     StartUpgradeCmd(workers[i]));  
    20.         }       
    21.     }  
    22.  
    23.     // 收到工作实体回应的升级完毕命令消息时,会触发该方法被调用 
    24.     public void finishUpgrade(String worker_name) {  
    25.         nfinished++;  
    26.         if(nfinished == workers.length){  
    27.             for(int i=0; i<workers.length; i++)  
    28.                 workers[i].getControlQueue().enqueue(new  
    29.                     ContineWorkCmd(workers[i]));  
    30.  
    31.         }  
    32.     }  
    33.      
    34.     ......  
    35.  
    36. }  
    37.  
    38. // 工作实体关键代码 
    39. class Worker extends ActiveObject{  
    40.     UpgradeController ugc;  
    41.     HotswapCL hscl;  
    42.     IFoo foo;  
    43.     String state = "hello world!";  
    44.      
    45.     ......  
    46.     
    47.     // 收到升级控制实体的准备升级命令消息时,会触发该方法被调用 
    48.     public void prepareUpgrade() {  
    49.         switchToControlQueue();  
    50.         ugc.getMsgQueue().enqueue(new ReadyForUpdateCMD(ugc,this));  
    51.     }  
    52.  
    53.     // 收到升级控制实体的开始升级命令消息时,会触发该方法被调用 
    54.     public void startUpgrade(String worker_name) {  
    55.         doUpgrade();  
    56.         ugc.getMsgQueue().enqueue(new FinishUpgradeCMD(ugc,this));  
    57.     }  
    58.  
    59.     // 收到升级控制实体的继续工作命令消息时,会触发该方法被调用 
    60.     public void continueWork(String worker_name) {  
    61.         switchToTaskQueue();  
    62.     }  
    63.  
    64.     // 收到定时命令消息时,会触发该方法被调用 
    65.     public void doWork() {  
    66.         foo.sayHello();     
    67.     }  
    68.  
    69.     // 实际升级动作 
    70.     private void doUpgrade() {  
    71.         hscl = new HowswapCL("../swap", new String[]{"Foo"});  
    72.         Class cls = hscl.loadClass("Foo");  
    73.         foo = (IFoo)cls.newInstance();  
    74.         foo.SetState(state);  
    75.     }  
    76. }  
    77.  
    78. //IFoo 接口定义 
    79. interface IFoo {  
    80.     void SetState(String);  
    81.     void sayHello();  

    在Foo类第一个版本的实现中,只是把设置进来的字符串直接打印出来。在第二个版本中,会先把设置进来的字符串变为大写,然后打印出来。例子很简单,旨在表达规则或者算法方面的升级变化。另外,我们并没有提及诸如:消息超时、升级失败等方面的异常情况,这在实际产品开发中是必须要考虑的。

posted @ 2010-03-04 13:16  林强  阅读(165)  评论(0编辑  收藏  举报