在线升级系统的设计原则

在上小节中,我们给出了一个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();   
82. }

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