Java类中热替换的概念、设计与实现(4)
在线升级系统的设计原则
在上小节中,我们给出了一个Java类热替换的实例,掌握了这项技术,就具备了实现在线升级系统的基础。但是,对于一个真正的产品系统来说,升级本省就是一项非常复杂的工程,如果要在线升级,就会更加复杂。其中,实现类的热替换只是最后一步操作,在线升级的要求会对系统的整体设计带来深远的影响。下面我们来谈谈在线升级系统设计方面的一些原则:
◆在系统设计一开始,就要考虑系统的哪些部分是需要以后在线升级的,哪些部分是稳定的
虽然我们可以把系统设计成任何一部分都是可以在线升级的,但是其成本是非常高昂的,也没有必要。因此,明确地界定出系统以后需要在线升级的部分是明智之举。这些部分常常是系统业务逻辑规则、算法等等。
◆设计出规范一致的系统状态转换方法
替换一个类仅仅是在线
◆明确出系统的升级控制协议
这个原则是关于系统在线升级的时机和流程控制的,不考虑系统的当前运行状态就贸然进行升级是一项非常危险的活动。因此在系统设计中,就要考虑并预留出系统在线升级的控制点,并定义清晰、明确的升级协议来协调、控制多个升级实体的升级次序,以确保系统在升级的任何时刻都处在一个确定的状态下。
◆考虑到升级失败时的回退机制
即使我们做了非常缜密细致的设计,还是难以从根本上保证系统升级一定是成功的,对于大型分布式系统来说尤其如此。因此在系统设计时,要考虑升级失败后的回退机制。
在线升级系统实例
首先,我们来简单介绍一下这个实例的结构组成和要完成的工作。在我们的例子中,主要有三个实体,一个是升级控制实体,两个是工作实体,都基于ActiveObject实现,通过命令消息进行通信(关于ActiveObject的详细信息,可以参见作者的另外一篇文章“构建Java并发模型框架”)。
升级控制实体以RMI的方式对外提供了一个管理命令接口,用以接收外部的在线升级命令。工作实体有两个消息队列,一个用以接收分配给它的任务(我们用定时器定时给它发送任务命令消息),我们称其为任务队列;另一个用于和升级控制实体交互,协作完成升级过程,我们称其为控制队列。工作实体中的任务很简单,就是使用我们前面介绍的Foo类简单地打印出一个字符串,不过这次字符串作为状态保存在工作实体中,动态设置给Foo类的实例的。升级的协议流程如下:
当升级控制实体接收到来自RMI的在线升级命令时,它会向两个工作实体的任务队列中发送一条准备升级消息,然后等待回应。当工作实体在任务队列中收到准备升级消息时,会立即给升级控制实体发送一条准备就绪消息,然后切换到控制队列等待进一步的升级指令。升级控制实体收齐这两个工作实体发来的准备就绪消息后,就给这两个工作实体的控制队列各发送一条开始升级消息,然后等待结果。工作实体收到开始升级消息后,进行实际的升级工作,也就是我们前面讲述的热替换类。然后,给升级控制实体发送升级完毕消息。升级控制实体收到来自两个工作实体的升级完毕消息后,会给这两个工作实体的控制队列各发送一条继续工作消息,工作实体收到继续工作消息后,切换到任务队列继续工作,升级过程结束。主要的代码片段如下(略去命令消息的定义和执行细节):
- // 升级控制实体关键代码
- class UpgradeController extends ActiveObject{
- int nready = 0;
- int nfinished = 0;
- Worker[] workers;
- ......
- // 收到外部升级命令消息时,会触发该方法被调用
- public void askForUpgrade() {
- for(int i=0; i<workers.length; i++)
- workers[i].getTaskQueue().enqueue(new PrepareUpgradeCmd(workers[i]));
- }
- // 收到工作实体回应的准备就绪命令消息时,会触发该方法被调用
- public void readyForUpgrade(String worker_name) {
- nready++;
- if(nready == workers.length){
- for(int i=0; i<workers.length; i++)
- workers[i].getControlQueue().enqueue(new
- StartUpgradeCmd(workers[i]));
- }
- }
- // 收到工作实体回应的升级完毕命令消息时,会触发该方法被调用
- public void finishUpgrade(String worker_name) {
- nfinished++;
- if(nfinished == workers.length){
- for(int i=0; i<workers.length; i++)
- workers[i].getControlQueue().enqueue(new
- ContineWorkCmd(workers[i]));
- }
- }
- ......
- }
- // 工作实体关键代码
- class Worker extends ActiveObject{
- UpgradeController ugc;
- HotswapCL hscl;
- IFoo foo;
- String state = "hello world!";
- ......
- // 收到升级控制实体的准备升级命令消息时,会触发该方法被调用
- public void prepareUpgrade() {
- switchToControlQueue();
- ugc.getMsgQueue().enqueue(new ReadyForUpdateCMD(ugc,this));
- }
- // 收到升级控制实体的开始升级命令消息时,会触发该方法被调用
- public void startUpgrade(String worker_name) {
- doUpgrade();
- ugc.getMsgQueue().enqueue(new FinishUpgradeCMD(ugc,this));
- }
- // 收到升级控制实体的继续工作命令消息时,会触发该方法被调用
- public void continueWork(String worker_name) {
- switchToTaskQueue();
- }
- // 收到定时命令消息时,会触发该方法被调用
- public void doWork() {
- foo.sayHello();
- }
- // 实际升级动作
- private void doUpgrade() {
- hscl = new HowswapCL("../swap", new String[]{"Foo"});
- Class cls = hscl.loadClass("Foo");
- foo = (IFoo)cls.newInstance();
- foo.SetState(state);
- }
- }
- //IFoo 接口定义
- interface IFoo {
- void SetState(String);
- void sayHello();
- }
在Foo类第一个版本的实现中,只是把设置进来的字符串直接打印出来。在第二个版本中,会先把设置进来的字符串变为大写,然后打印出来。例子很简单,旨在表达规则或者算法方面的升级变化。另外,我们并没有提及诸如:消息超时、升级失败等方面的异常情况,这在实际产品开发中是必须要考虑的。