線上升級系統的設計原則
在上小節中,我們給出了一個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類第一個版本的實現中,只是把設定進來的字串直接列印出來。在第二個版本中,會先把設定進來的字串變為大寫,然後列印出來。例子很簡單,旨在表達規則或者演算法方面的升級變化。另外,我們並沒有提及諸如:訊息逾時、升級失敗等方面的異常情況,這在實際產品開發中是必須要考慮的。