Java 類的熱替換 —— 概念、設(shè)計(jì)與實(shí)現(xiàn)
構(gòu)建基于 Java 的在線升級(jí)系統(tǒng)
簡(jiǎn)介: 對(duì)于許多關(guān)鍵性業(yè)務(wù)或者龐大的 Java 系統(tǒng)來(lái)說(shuō),如果必須暫停系統(tǒng)服務(wù)才能進(jìn)行系統(tǒng)升級(jí),既會(huì)大大影響到系統(tǒng)的可用性,同時(shí)也增加了系統(tǒng)的管理和維護(hù)成本。因此,如果能夠方便地在不停止系統(tǒng)業(yè)務(wù)的情況下進(jìn)行系統(tǒng)升級(jí),則可以很好地解決上述問(wèn)題。在本文中,我們將基于實(shí)例,對(duì)構(gòu)建在線升級(jí) Java 系統(tǒng)的基礎(chǔ)技術(shù)和設(shè)計(jì)原則進(jìn)行了深入的講解。相信讀者能夠根據(jù)文中的技術(shù)構(gòu)建出自己的在線升級(jí)系統(tǒng)來(lái)。
發(fā)布日期: 2010 年 1 月 14 日
級(jí)別: 中級(jí)
訪問(wèn)情況 1651 次瀏覽
建議: 0 (添加評(píng)論)
在本文中,我們將不對(duì) Java ClassLoader 的細(xì)節(jié)進(jìn)行過(guò)于詳細(xì)的講解,而是關(guān)注于和構(gòu)建在線升級(jí)系統(tǒng)相關(guān)的基礎(chǔ)概念。關(guān)于 ClassLoader 的詳細(xì)細(xì)節(jié)許多資料可以參考,有興趣的讀者可以自行研讀。
要構(gòu)建在線升級(jí)系統(tǒng),一個(gè)重要的技術(shù)就是能夠?qū)崿F(xiàn) Java 類的熱替換 —— 也就是在不停止正在運(yùn)行的系統(tǒng)的情況下進(jìn)行類(對(duì)象)的升級(jí)替換。而 Java 的 ClassLoader 正是實(shí)現(xiàn)這項(xiàng)技術(shù)的基礎(chǔ)。
在 Java 中,類的實(shí)例化流程分為兩個(gè)部分:類的加載和類的實(shí)例化。類的加載又分為顯式加載和隱式加載。大家使用 new 關(guān)鍵字創(chuàng)建類實(shí)例時(shí),其實(shí)就隱式地包含了類的加載過(guò)程。對(duì)于類的顯式加載來(lái)說(shuō),比較常用的是 Class.forName。其實(shí),它們都是通過(guò)調(diào)用 ClassLoader 類的 loadClass 方法來(lái)完成類的實(shí)際加載工作的。直接調(diào)用 ClassLoader 的 loadClass 方法是另外一種不常用的顯式加載類的技術(shù)。
圖 1. Java 類加載器層次結(jié)構(gòu)圖

ClassLoader 在加載類時(shí)有一定的層次關(guān)系和規(guī)則。在 Java 中,有四種類型的類加載器,分別為:BootStrapClassLoader、ExtClassLoader、AppClassLoader 以及用戶自定義的 ClassLoader。這四種類加載器分別負(fù)責(zé)不同路徑的類的加載,并形成了一個(gè)類加載的層次結(jié)構(gòu)。
BootStrapClassLoader 處于類加載器層次結(jié)構(gòu)的最高層,負(fù)責(zé) sun.boot.class.path 路徑下類的加載,默認(rèn)為 jre/lib 目錄下的核心 API 或 -Xbootclasspath 選項(xiàng)指定的 jar 包。ExtClassLoader 的加載路徑為 java.ext.dirs,默認(rèn)為 jre/lib/ext 目錄或者 -Djava.ext.dirs 指定目錄下的 jar 包加載。AppClassLoader 的加載路徑為 java.class.path,默認(rèn)為環(huán)境變量 CLASSPATH 中設(shè)定的值。也可以通過(guò) -classpath 選型進(jìn)行指定。用戶自定義 ClassLoader 可以根據(jù)用戶的需要定制自己的類加載過(guò)程,在運(yùn)行期進(jìn)行指定類的動(dòng)態(tài)實(shí)時(shí)加載。
這四種類加載器的層次關(guān)系圖如 圖 1 所示。一般來(lái)說(shuō),這四種類加載器會(huì)形成一種父子關(guān)系,高層為低層的父加載器。在進(jìn)行類加載時(shí),首先會(huì)自底向上挨個(gè)檢查是否已經(jīng)加載了指定類,如果已經(jīng)加載則直接返回該類的引用。如果到最高層也沒(méi)有加載過(guò)指定類,那么會(huì)自頂向下挨個(gè)嘗試加載,直到用戶自定義類加載器,如果還不能成功,就會(huì)拋出異常。Java 類的加載過(guò)程如 圖 2 所示。
圖 2. Java 類的加載過(guò)程

每個(gè)類加載器有自己的名字空間,對(duì)于同一個(gè)類加載器實(shí)例來(lái)說(shuō),名字相同的類只能存在一個(gè),并且僅加載一次。不管該類有沒(méi)有變化,下次再需要加載時(shí),它只是從自己的緩存中直接返回已經(jīng)加載過(guò)的類引用。
我們編寫的應(yīng)用類默認(rèn)情況下都是通過(guò) AppClassLoader 進(jìn)行加載的。當(dāng)我們使用 new 關(guān)鍵字或者 Class.forName 來(lái)加載類時(shí),所要加載的類都是由調(diào)用 new 或者 Class.forName 的類的類加載器(也是 AppClassLoader)進(jìn)行加載的。要想實(shí)現(xiàn) Java 類的熱替換,首先必須要實(shí)現(xiàn)系統(tǒng)中同名類的不同版本實(shí)例的共存,通過(guò)上面的介紹我們知道,要想實(shí)現(xiàn)同一個(gè)類的不同版本的共存,我們必須要通過(guò)不同的類加載器來(lái)加載該類的不同版本。另外,為了能夠繞過(guò) Java 類的既定加載過(guò)程,我們需要實(shí)現(xiàn)自己的類加載器,并在其中對(duì)類的加載過(guò)程進(jìn)行完全的控制和管理。
為了能夠完全掌控類的加載過(guò)程,我們的定制類加載器需要直接從 ClassLoader 繼承。首先我們來(lái)介紹一下 ClassLoader 類中和熱替換有關(guān)的的一些重要方法。
- findLoadedClass:每個(gè)類加載器都維護(hù)有自己的一份已加載類名字空間,其中不能出現(xiàn)兩個(gè)同名的類。凡是通過(guò)該類加載器加載的類,無(wú)論是直接的還是間接的,都保存在自己的名字空間中,該方法就是在該名字空間中尋找指定的類是否已存在,如果存在就返回給類的引用,否則就返回 null。這里的直接是指,存在于該類加載器的加載路徑上并由該加載器完成加載,間接是指,由該類加載器把類的加載工作委托給其他類加載器完成類的實(shí)際加載。
- getSystemClassLoader:Java2 中新增的方法。該方法返回系統(tǒng)使用的 ClassLoader。可以在自己定制的類加載器中通過(guò)該方法把一部分工作轉(zhuǎn)交給系統(tǒng)類加載器去處理。
- defineClass:該方法是 ClassLoader 中非常重要的一個(gè)方法,它接收以字節(jié)數(shù)組表示的類字節(jié)碼,并把它轉(zhuǎn)換成 Class 實(shí)例,該方法轉(zhuǎn)換一個(gè)類的同時(shí),會(huì)先要求裝載該類的父類以及實(shí)現(xiàn)的接口類。
- loadClass:加載類的入口方法,調(diào)用該方法完成類的顯式加載。通過(guò)對(duì)該方法的重新實(shí)現(xiàn),我們可以完全控制和管理類的加載過(guò)程。
- resolveClass:鏈接一個(gè)指定的類。這是一個(gè)在某些情況下確保類可用的必要方法,詳見 Java 語(yǔ)言規(guī)范中“執(zhí)行”一章對(duì)該方法的描述。
了解了上面的這些方法,下面我們來(lái)實(shí)現(xiàn)一個(gè)定制的類加載器來(lái)完成這樣的加載流程:我們?yōu)樵擃惣虞d器指定一些必須由該類加載器直接加載的類集合,在該類加載器進(jìn)行類的加載時(shí),如果要加載的類屬于必須由該類加載器加載的集合,那么就由它直接來(lái)完成類的加載,否則就把類加載的工作委托給系統(tǒng)的類加載器完成。
在給出示例代碼前,有兩點(diǎn)內(nèi)容需要說(shuō)明一下:1、要想實(shí)現(xiàn)同一個(gè)類的不同版本的共存,那么這些不同版本必須由不同的類加載器進(jìn)行加載,因此就不能把這些類的加載工作委托給系統(tǒng)加載器來(lái)完成,因?yàn)樗鼈冎挥幸环荨?、為了做到這一點(diǎn),就不能采用系統(tǒng)默認(rèn)的類加載器委托規(guī)則,也就是說(shuō)我們定制的類加載器的父加載器必須設(shè)置為 null。該定制的類加載器的實(shí)現(xiàn)代碼如下:
清單 1. 定制的類加載器的實(shí)現(xiàn)代碼
class CustomCL extends ClassLoader { private String basedir; // 需要該類加載器直接加載的類文件的基目錄 private HashSet dynaclazns; // 需要由該類加載器直接加載的類名 public CustomCL(String basedir, String[] clazns) { super(null); // 指定父類加載器為 null this.basedir = basedir; dynaclazns = new HashSet(); loadClassByMe(clazns); } private void loadClassByMe(String[] clazns) { for (int i = 0; i < clazns.length; i++) { loadDirectly(clazns[i]); dynaclazns.add(clazns[i]); } } private Class loadDirectly(String name) { Class cls = null; StringBuffer sb = new StringBuffer(basedir); String classname = name.replace('.', File.separatorChar) + ".class"; sb.append(File.separator + classname); File classF = new File(sb.toString()); cls = instantiateClass(name,new FileInputStream(classF), classF.length()); return cls; } private Class instantiateClass(String name,InputStream fin,long len){ byte[] raw = new byte[(int) len]; fin.read(raw); fin.close(); return defineClass(name,raw,0,raw.length); } protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class cls = null; cls = findLoadedClass(name); if(!this.dynaclazns.contains(name) && cls == null) cls = getSystemClassLoader().loadClass(name); if (cls == null) throw new ClassNotFoundException(name); if (resolve) resolveClass(cls); return cls; } } |
在該類加載器的實(shí)現(xiàn)中,所有指定必須由它直接加載的類都在該加載器實(shí)例化時(shí)進(jìn)行了加載,當(dāng)通過(guò) loadClass 進(jìn)行類的加載時(shí),如果該類沒(méi)有加載過(guò),并且不屬于必須由該類加載器加載之列都委托給系統(tǒng)加載器進(jìn)行加載。理解了這個(gè)實(shí)現(xiàn),距離實(shí)現(xiàn)類的熱替換就只有一步之遙了,我們?cè)谙乱恍」?jié)對(duì)此進(jìn)行詳細(xì)的講解
在本小節(jié)中,我們將結(jié)合前面講述的類加載器的特性,并在上小節(jié)實(shí)現(xiàn)的自定義類加載器的基礎(chǔ)上實(shí)現(xiàn) Java 類的熱替換。首先我們把上小節(jié)中實(shí)現(xiàn)的類加載器的類名 CustomCL 更改為 HotswapCL,以明確表達(dá)我們的意圖。
現(xiàn)在來(lái)介紹一下我們的實(shí)驗(yàn)方法,為了簡(jiǎn)單起見,我們的包為默認(rèn)包,沒(méi)有層次,并且省去了所有錯(cuò)誤處理。要替換的類為 Foo,實(shí)現(xiàn)很簡(jiǎn)單,僅包含一個(gè)方法 sayHello:
清單 2. 待替換的示例類
public class Foo{ public void sayHello() { System.out.println("hello world! (version one)"); } } |
在當(dāng)前工作目錄下建立一個(gè)新的目錄 swap,把編譯好的 Foo.class 文件放在該目錄中。接下來(lái)要使用我們前面編寫的 HotswapCL 來(lái)實(shí)現(xiàn)該類的熱替換。具體的做法為:我們編寫一個(gè)定時(shí)器任務(wù),每隔 2 秒鐘執(zhí)行一次。其中,我們會(huì)創(chuàng)建新的類加載器實(shí)例加載 Foo 類,生成實(shí)例,并調(diào)用 sayHello 方法。接下來(lái),我們會(huì)修改 Foo 類中 sayHello 方法的打印內(nèi)容,重新編譯,并在系統(tǒng)運(yùn)行的情況下替換掉原來(lái)的 Foo.class,我們會(huì)看到系統(tǒng)會(huì)打印出更改后的內(nèi)容。定時(shí)任務(wù)的實(shí)現(xiàn)如下(其它代碼省略,請(qǐng)讀者自行補(bǔ)齊):
清單 3. 實(shí)現(xiàn)定時(shí)任務(wù)的部分代碼
public void run(){ try { // 每次都創(chuàng)建出一個(gè)新的類加載器 HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"}); Class cls = cl.loadClass("Foo"); Object foo = cls.newInstance(); Method m = foo.getClass().getMethod("sayHello", new Class[]{}); m.invoke(foo, new Object[]{}); } catch(Exception ex) { ex.printStackTrace(); } } |
編譯、運(yùn)行我們的系統(tǒng),會(huì)出現(xiàn)如下的打印:
圖 3. 熱替換前的運(yùn)行結(jié)果

好,現(xiàn)在我們把 Foo 類的 sayHello 方法更改為:
public void sayHello() { System.out.println("hello world! (version two)"); } |
在系統(tǒng)仍在運(yùn)行的情況下,編譯,并替換掉 swap 目錄下原來(lái)的 Foo.class 文件,我們?cè)倏纯雌聊坏拇蛴。婷畹氖虑榘l(fā)生了,新更改的類在線即時(shí)生效了,我們已經(jīng)實(shí)現(xiàn)了 Foo 類的熱替換。屏幕打印如下:
圖 4. 熱替換后的運(yùn)行結(jié)果

敏銳的讀者可能會(huì)問(wèn),為何不用把 foo 轉(zhuǎn)型為 Foo,直接調(diào)用其 sayHello 方法呢?這樣不是更清晰明了嗎?下面我們來(lái)解釋一下原因,并給出一種更好的方法。
如果我們采用轉(zhuǎn)型的方法,代碼會(huì)變成這樣:Foo foo = (Foo)cls.newInstance();
讀者如果跟隨本文進(jìn)行試驗(yàn)的話,會(huì)發(fā)現(xiàn)這句話會(huì)拋出 ClassCastException 異常,為什么嗎?因?yàn)樵?Java 中,即使是同一個(gè)類文件,如果是由不同的類加載器實(shí)例加載的,那么它們的類型是不相同的。在上面的例子中 cls 是由 HowswapCL 加載的,而 foo 變量類型聲名和轉(zhuǎn)型里的 Foo 類卻是由 run 方法所屬的類的加載器(默認(rèn)為 AppClassLoader)加載的,因此是完全不同的類型,所以會(huì)拋出轉(zhuǎn)型異常。
那么通過(guò)接口調(diào)用是不是就行了呢?我們可以定義一個(gè) IFoo 接口,其中聲名 sayHello 方法,F(xiàn)oo 實(shí)現(xiàn)該接口。也就是這樣:IFoo foo = (IFoo)cls.newInstance();
本來(lái)該方法也會(huì)有同樣的問(wèn)題的,因?yàn)橥獠柯暶娃D(zhuǎn)型部分的 IFoo 是由 run 方法所屬的類加載器加載的,而 Foo 類定義中 implements IFoo 中的 IFoo 是由 HotswapCL 加載的,因此屬于不同的類型轉(zhuǎn)型還是會(huì)拋出異常的,但是由于我們?cè)趯?shí)例化 HotswapCL 時(shí)是這樣的:
HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"});
其中僅僅指定 Foo 類由 HotswapCL 加載,而其實(shí)現(xiàn)的 IFoo 接口文件會(huì)委托給系統(tǒng)類加載器加載,因此轉(zhuǎn)型成功,采用接口調(diào)用的代碼如下:
清單 4. 采用接口調(diào)用的代碼
public void run(){ try { HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"}); Class cls = cl.loadClass("Foo"); IFoo foo = (IFoo)cls.newInstance(); foo.sayHello(); } catch(Exception ex) { ex.printStackTrace(); } } |
確實(shí),簡(jiǎn)潔明了了很多。在我們的實(shí)驗(yàn)中,每當(dāng)定時(shí)器調(diào)度到 run 方法時(shí),我們都會(huì)創(chuàng)建一個(gè)新的 HotswapCL 實(shí)例,在產(chǎn)品代碼中,無(wú)需如此,僅當(dāng)需要升級(jí)替換時(shí)才去創(chuàng)建一個(gè)新的類加載器實(shí)例。
在線升級(jí)系統(tǒng)的設(shè)計(jì)原則
在上小節(jié)中,我們給出了一個(gè) Java 類熱替換的實(shí)例,掌握了這項(xiàng)技術(shù),就具備了實(shí)現(xiàn)在線升級(jí)系統(tǒng)的基礎(chǔ)。但是,對(duì)于一個(gè)真正的產(chǎn)品系統(tǒng)來(lái)說(shuō),升級(jí)本省就是一項(xiàng)非常復(fù)雜的工程,如果要在線升級(jí),就會(huì)更加復(fù)雜。其中,實(shí)現(xiàn)類的熱替換只是最后一步操作,在線升級(jí)的要求會(huì)對(duì)系統(tǒng)的整體設(shè)計(jì)帶來(lái)深遠(yuǎn)的影響。下面我們來(lái)談?wù)勗诰€升級(jí)系統(tǒng)設(shè)計(jì)方面的一些原則:
- 在系統(tǒng)設(shè)計(jì)一開始,就要考慮系統(tǒng)的哪些部分是需要以后在線升級(jí)的,哪些部分是穩(wěn)定的。
雖然我們可以把系統(tǒng)設(shè)計(jì)成任何一部分都是可以在線升級(jí)的,但是其成本是非常高昂的,也沒(méi)有必要。因此,明確地界定出系統(tǒng)以后需要在線升級(jí)的部分是明智之舉。這些部分常常是系統(tǒng)業(yè)務(wù)邏輯規(guī)則、算法等等。
- 設(shè)計(jì)出規(guī)范一致的系統(tǒng)狀態(tài)轉(zhuǎn)換方法。
替換一個(gè)類僅僅是在線升級(jí)系統(tǒng)所要做的工作中的一個(gè)步驟,為了使系統(tǒng)能夠在升級(jí)后正常運(yùn)行,就必須保持升級(jí)前后系統(tǒng)狀態(tài)的一致性。因此,在設(shè)計(jì)時(shí)要考慮需要在線升級(jí)的部分所涉及的系統(tǒng)狀態(tài)有哪些,把這些狀態(tài)設(shè)計(jì)成便于獲取、設(shè)置和轉(zhuǎn)換的,并用一致的方式來(lái)進(jìn)行。
- 明確出系統(tǒng)的升級(jí)控制協(xié)議。
這個(gè)原則是關(guān)于系統(tǒng)在線升級(jí)的時(shí)機(jī)和流程控制的,不考慮系統(tǒng)的當(dāng)前運(yùn)行狀態(tài)就貿(mào)然進(jìn)行升級(jí)是一項(xiàng)非常危險(xiǎn)的活動(dòng)。因此在系統(tǒng)設(shè)計(jì)中, 就要考慮并預(yù)留出系統(tǒng)在線升級(jí)的控制點(diǎn), 并定義清晰、明確的升級(jí)協(xié)議來(lái)協(xié)調(diào)、控制多個(gè)升級(jí)實(shí)體的升級(jí)次序,以確保系統(tǒng)在升級(jí)的任何時(shí)刻都處在一個(gè)確定的狀態(tài)下。
- 考慮到升級(jí)失敗時(shí)的回退機(jī)制。
即使我們做了非常縝密細(xì)致的設(shè)計(jì),還是難以從根本上保證系統(tǒng)升級(jí)一定是成功的,對(duì)于大型分布式系統(tǒng)來(lái)說(shuō)尤其如此。因此在系統(tǒng)設(shè)計(jì)時(shí),要考慮升級(jí)失敗后的回退機(jī)制。
好了,本小節(jié)我們簡(jiǎn)單介紹了在線升級(jí)系統(tǒng)設(shè)計(jì)時(shí)的幾個(gè)重要的原則,下一小節(jié)我們將給出一個(gè)簡(jiǎn)單的實(shí)例,來(lái)演示一下如何來(lái)實(shí)現(xiàn)一個(gè)在線升級(jí)系統(tǒng)。
首先,我們來(lái)簡(jiǎn)單介紹一下這個(gè)實(shí)例的結(jié)構(gòu)組成和要完成的工作。在我們的例子中,主要有三個(gè)實(shí)體,一個(gè)是升級(jí)控制實(shí)體,兩個(gè)是工作實(shí)體,都基于 ActiveObject 實(shí)現(xiàn),通過(guò)命令消息進(jìn)行通信(關(guān)于 ActiveObject 的詳細(xì)信息,可以參見作者的另外一篇文章“構(gòu)建 Java 并發(fā)模型框架”)。
升級(jí)控制實(shí)體以 RMI 的方式對(duì)外提供了一個(gè)管理命令接口,用以接收外部的在線升級(jí)命令。工作實(shí)體有兩個(gè)消息隊(duì)列,一個(gè)用以接收分配給它的任務(wù)(我們用定時(shí)器定時(shí)給它發(fā)送任務(wù)命令消息),我們稱其為任務(wù)隊(duì)列;另一個(gè)用于和升級(jí)控制實(shí)體交互,協(xié)作完成升級(jí)過(guò)程,我們稱其為控制隊(duì)列。工作實(shí)體中的任務(wù)很簡(jiǎn)單,就是使用我們前面介紹的 Foo 類簡(jiǎn)單地打印出一個(gè)字符串,不過(guò)這次字符串作為狀態(tài)保存在工作實(shí)體中,動(dòng)態(tài)設(shè)置給 Foo 類的實(shí)例的。升級(jí)的協(xié)議流程如下:
當(dāng)升級(jí)控制實(shí)體接收到來(lái)自 RMI 的在線升級(jí)命令時(shí),它會(huì)向兩個(gè)工作實(shí)體的任務(wù)隊(duì)列中發(fā)送一條準(zhǔn)備升級(jí)消息,然后等待回應(yīng)。當(dāng)工作實(shí)體在任務(wù)隊(duì)列中收到準(zhǔn)備升級(jí)消息時(shí),會(huì)立即給升級(jí)控制實(shí)體發(fā)送一條準(zhǔn)備就緒消息,然后切換到控制隊(duì)列等待進(jìn)一步的升級(jí)指令。升級(jí)控制實(shí)體收齊這兩個(gè)工作實(shí)體發(fā)來(lái)的準(zhǔn)備就緒消息后,就給這兩個(gè)工作實(shí)體的控制隊(duì)列各發(fā)送一條開始升級(jí)消息,然后等待結(jié)果。工作實(shí)體收到開始升級(jí)消息后,進(jìn)行實(shí)際的升級(jí)工作,也就是我們前面講述的熱替換類。然后,給升級(jí)控制實(shí)體發(fā)送升級(jí)完畢消息。升級(jí)控制實(shí)體收到來(lái)自兩個(gè)工作實(shí)體的升級(jí)完畢消息后,會(huì)給這兩個(gè)工作實(shí)體的控制隊(duì)列各發(fā)送一條繼續(xù)工作消息,工作實(shí)體收到繼續(xù)工作消息后,切換到任務(wù)隊(duì)列繼續(xù)工作。升級(jí)過(guò)程結(jié)束。
主要的代碼片段如下(略去命令消息的定義和執(zhí)行細(xì)節(jié)):
清單 5. 主要的代碼片段
// 升級(jí)控制實(shí)體關(guān)鍵代碼 class UpgradeController extends ActiveObject{ int nready = 0; int nfinished = 0; Worker[] workers; ...... // 收到外部升級(jí)命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 public void askForUpgrade() { for(int i=0; i<workers.length; i++) workers[i].getTaskQueue().enqueue(new PrepareUpgradeCmd(workers[i])); } // 收到工作實(shí)體回應(yīng)的準(zhǔn)備就緒命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 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])); } } // 收到工作實(shí)體回應(yīng)的升級(jí)完畢命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 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])); } } ...... } // 工作實(shí)體關(guān)鍵代碼 class Worker extends ActiveObject{ UpgradeController ugc; HotswapCL hscl; IFoo foo; String state = "hello world!"; ...... // 收到升級(jí)控制實(shí)體的準(zhǔn)備升級(jí)命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 public void prepareUpgrade() { switchToControlQueue(); ugc.getMsgQueue().enqueue(new ReadyForUpdateCMD(ugc,this)); } // 收到升級(jí)控制實(shí)體的開始升級(jí)命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 public void startUpgrade(String worker_name) { doUpgrade(); ugc.getMsgQueue().enqueue(new FinishUpgradeCMD(ugc,this)); } // 收到升級(jí)控制實(shí)體的繼續(xù)工作命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 public void continueWork(String worker_name) { switchToTaskQueue(); } // 收到定時(shí)命令消息時(shí),會(huì)觸發(fā)該方法被調(diào)用 public void doWork() { foo.sayHello(); } // 實(shí)際升級(jí)動(dòng)作 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 類第一個(gè)版本的實(shí)現(xiàn)中,只是把設(shè)置進(jìn)來(lái)的字符串直接打印出來(lái)。在第二個(gè)版本中,會(huì)先把設(shè)置進(jìn)來(lái)的字符串變?yōu)榇髮懀缓蟠蛴〕鰜?lái)。例子很簡(jiǎn)單,旨在表達(dá)規(guī)則或者算法方面的升級(jí)變化。另外,我們并沒(méi)有提及諸如:消息超時(shí)、升級(jí)失敗等方面的異常情況,這在實(shí)際產(chǎn)品開發(fā)中是必須要考慮的。
在本文中,我們對(duì) Java 在線升級(jí)系統(tǒng)中設(shè)計(jì)的基礎(chǔ)技術(shù):類的熱替換,進(jìn)行了詳細(xì)的講解。此外,還給出了在線升級(jí)系統(tǒng)設(shè)計(jì)時(shí)的一些主要指導(dǎo)原則。為了使讀者更好地理解這些技術(shù)和原則,我們?cè)谧詈蠼o出了一個(gè)在線升級(jí)系統(tǒng)的實(shí)例。值得注意的是,構(gòu)建在線升級(jí)系統(tǒng)不僅僅是一個(gè)技術(shù)問(wèn)題,還牽扯到很多管理方面的因素,比如:如何管理、部署系統(tǒng)中的可在線升級(jí)部分和不可在線升級(jí)部分以降低系統(tǒng)的管理、維護(hù)成本等。希望本文在讀者構(gòu)建自己的在線升級(jí)系統(tǒng)時(shí)能夠提供一些幫助。
學(xué)習(xí)
- 參考:“JDK 5.0 的文檔”。
- 參考:“Making reliable distributed systems in the presence of software errors”。
- “了解 Java ClassLoader”(developerWorks,2001 年 6 月):創(chuàng)建自己的 ClassLoader 可以以實(shí)用且有趣的方式定制 JVM,這樣可以讓您徹底重新定義如何將類文件引入系統(tǒng)。
- “構(gòu)建 Java 并發(fā)模型框架”(developerWorks,2002 年 4 月):介紹通過(guò)構(gòu)建一個(gè)并發(fā)模型框架,使得開發(fā)多線程的應(yīng)用變得容易。
- developerWorks Java 技術(shù)專區(qū):查找關(guān)于 Java 編程各方面的數(shù)百篇文章。
討論
- 加入 developerWorks 社區(qū)。
- 查看 developerWorks 博客的最新信息。
內(nèi)容
標(biāo)簽
熱門文章標(biāo)簽 |
我的文章標(biāo)簽跳轉(zhuǎn)到標(biāo)簽列表
熱門標(biāo)簽
- _unassigned (26)
- 1 (7)
- 2 (8)
- 5 (3)
- ajax (16)
- ant (14)
- apache (23)
- apache_axis (3)
- api (29)
- best_practic... (131)
- developer_kits (3)
- eclipse (10)
- ejb_(enterpris... (5)
- generic (11)
- html (5)
- i/o (4)
- ibatis (3)
- internationali... (3)
- j2ee (5)
- j2ee_(java_2_... (12)
- j2se_(java_2_... (36)
- java (112)
- java_技術(shù) (84)
- java_入門 (7)
- java-spring (8)
- javascript (8)
- java多線程 (3)
- jpa (9)
- js (3)
- jsf (8)
- jsf_(javaserve... (7)
- jsp_(javaserve... (7)
- junit (3)
- jvm_(java_virt... (8)
- maven (4)
- nio (3)
- performance (3)
- rest (3)
- selenium (5)
- servlet (38)
- spring (38)
- struts (6)
- test (4)
- tomcat (22)
- uml (5)
- web (13)
- web_服務(wù) (13)
- web_應(yīng)用 (6)
- xml (13)
- 安全 (14)
- 編碼 (7)
- 標(biāo)準(zhǔn) (6)
- 部分 (7)
- 部分: (6)
- 持久性 (9)
- 第 (4)
- 調(diào)試 (7)
- 多線程 (14)
- 工作原理解析 (6)
- 管理 (4)
- 架構(gòu)模式 (4)
- 開放源碼 (72)
- 模式 (9)
- 配置 (13)
- 設(shè)計(jì)模式 (17)
- 事務(wù) (4)
- 數(shù)據(jù)訪問(wèn) (7)
- 數(shù)據(jù)庫(kù)和數(shù)據(jù)管理 (18)
- 算法 (10)
- 體系架構(gòu) (5)
- 網(wǎng)絡(luò) (4)
- 無(wú)線技術(shù) (11)
- 系統(tǒng)架構(gòu)與設(shè)計(jì)模式 (6)
- 線程 (7)
- 性能 (14)
- 序列化 (7)
- 序列化的高級(jí)認(rèn)識(shí) (4)
- 應(yīng)用架構(gòu) (5)
- 應(yīng)用開發(fā) (294)
- 用戶界面 (4)
熱門標(biāo)簽結(jié)束
我的標(biāo)簽
查看熱門標(biāo)簽
我的標(biāo)簽結(jié)束
- _unassigned (26)
- 1 (7)
- 2 (8)
- 5 (3)
- ajax (16)
- ant (14)
- apache (23)
- apache_axis (3)
- api (29)
- best_practic... (131)
- developer_kits (3)
- eclipse (10)
- ejb_(enterpris... (5)
- generic (11)
- html(5)
- i/o (4)
- ibatis (3)
- internationali... (3)
- j2ee (5)
- j2ee_(java_2_... (12)
- j2se_(java_2_... (36)
- java (112)
- java_技術(shù) (84)
- java_入門 (7)
- java-spring (8)
- javascript (8)
- java多線程 (3)
- jpa (9)
- js (3)
- jsf (8)
- jsf_(javaserve... (7)
- jsp_(javaserve... (7)
- junit (3)
- jvm_(java_virt... (8)
- maven (4)
- nio (3)
- performance (3)
- rest (3)
- selenium (5)
- servlet (38)
- spring (38)
- struts (6)
- test (4)
- tomcat (22)
- uml (5)
- web (13)
- web_服務(wù) (13)
- web_應(yīng)用 (6)
- xml (13)
- 安全 (14)
- 編碼 (7)
- 標(biāo)準(zhǔn) (6)
- 部分 (7)
- 部分: (6)
- 持久性 (9)
- 第 (4)
- 調(diào)試 (7)
- 多線程 (14)
- 工作原理解析 (6)
- 管理 (4)
- 架構(gòu)模式 (4)
- 開放源碼 (72)
- 模式 (9)
- 配置 (13)
- 設(shè)計(jì)模式 (17)
- 事務(wù) (4)
- 數(shù)據(jù)訪問(wèn)(7)
- 數(shù)據(jù)庫(kù)和數(shù)據(jù)管理 (18)
- 算法 (10)
- 體系架構(gòu) (5)
- 網(wǎng)絡(luò) (4)
- 無(wú)線技術(shù) (11)
- 系統(tǒng)架構(gòu)與設(shè)計(jì)模式 (6)
- 線程 (7)
- 性能 (14)
- 序列化(7)
- 序列化的高級(jí)認(rèn)識(shí) (4)
- 應(yīng)用架構(gòu) (5)
- 應(yīng)用開發(fā) (294)
- 用戶界面 (4)
查看方式云 | 列表
快來(lái)添加第一條評(píng)論