程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 高密度Java應用部署的實踐

高密度Java應用部署的實踐

編輯:關於JAVA

傳統的Java應用部署模式,一般遵循“硬件->操作系統->JVM->Java應用”這種自底向上的部署結構,其中JEE應用可以細化為“硬件->操作系統->JVM->JEE容器->JEE應用”的部署結構。這種部署結構往往比較重,操作系統、JVM和JEE容器造成的overhead很高,而很多時候一個Java應用並不需要跑滿整個硬件的資源,導致這種模式的資源利用率是比較低的。

而另一方面,硬件虛擬化技術逐漸成熟:VMware Hypervisor、Xen、KVM、Power LPAR等技術能夠幫助我們在同一個硬件上部署多個操作系統實例,而時下流行的OS Container技術如LXC、Docker等,則是把操作系統虛擬化為多個實例,實現更輕量級的虛擬化。無論哪個層面的虛擬化,其目的都是對資源利用率更加高效的追求,從而成為如今構建雲計算平台底層架構的基礎技術。

Java應用也可以通過同樣的思路來實現高密度的部署。JVM虛擬化是比OS虛擬化更高一層的做法,可以更大程度的提高資源利用率,降低平均應用的部署成本。本文將介紹Multi-tenant JVM這一方案實現高密度Java應用部署的一些特點和思路。

背景介紹

早在2004年,Sun公司就提出過Java應用虛擬化這方面的想法。當時Grzegorz Czajkowski領導了一個叫做巴塞羅那的研究項目,該項目基於Java HotSpot虛擬1.5版本開發了Multi-Tasking Virtual Machine(MVM)。MVM的目的旨在提高Java程序的啟動速度,節省內存開銷。不過自從Sun被甲骨文收購後,我們沒有聽到關於該項目的任何新的進展。

盡管我們沒有看到MVM成功產品化,不過它卻留下兩個JSR規范:JSR121和JSR284。對於JSR284,目前在java.net上有一個實現它的孵化項目。

從2009年開始,我所在的IBM Java團隊開始研究Java應用的SaaS化方案,即讓一個應用實例服務於多個租戶。為了保證多個租戶在使用同一個應用實例時候數據的隔離,該方案在應用這個層面做了一些Bytecode Instrument(BCI)的工作,主要通過改寫getstatic/putstatic使每個租戶有獨立的類的靜態數據拷貝而沒有相互影響。但是,該方案在Bytecode層面更改帶來的額外性能開銷, 以及Java Reflection等訪問帶來的安全性/正確性的問題。 而且,除了數據上的隔離,也需要針對關鍵性的資源譬如CPU、Heap、IO等資源的使用進行管理,於是該方案下沉到了JVM層面,形成現在的多租戶JVM(Multi-tenant JVM)方案。

Multi-tenant JVM是JVM層面的虛擬化,其思路是把多個Java應用部署在同一個JVM上,讓這些應用共享底層的GC、JIT、Java運行時庫等基礎組件。除了IBM的團隊之外,愛爾蘭的Waratek公司也實現了多租戶的JVM。和IBM Multi-tenant JVM類似,Waratek允許多個應用運行在同一個CloudVM上,每一個應用運行在一個叫Java Virtual Container(JVC)的容器裡。從現有公開的資料開看,IBM Multi-tenant JVM是基於Java 7的,而Waratek是基於Java 6的,兩者支持的CPU架構和平台也有所不同。

此外,JEE方面在兩年前也有討論計劃增加對PaaS和多租戶的支持,這項提議旨在定義PaaS環境下如何使得JEE應用支持多租戶,保證不同租戶在使用這些應用時相互隔離,以及資源方面的管理(如JMS資源),不過該項提議已經推遲到JEE 8。

除了提升部署密度之外,多租戶的另一項好處在於應用啟動的加速。快速的程序啟動受益於不同的應用共享同一個JVM,我們稱之為javad。Java核心的類庫在javad運行後,不再需要被重新裝載和定義。你也許可以用Nailgun來加速你的啟動時間,但Nailgun的問題是沒有安全的數據隔離,這包括類的靜態數據以及Java屬性值,而且Nailgun在易用性等方面也不如Multi-tenent JVM。

多租戶JVM的實現思路

跟傳統JVM相比,多租戶JVM的主要工作圍繞隔離而進行,其針對JVM/JDK的改動主要實現三個方面的目標:

租戶之間的數據隔離

Java類庫支持多租戶語境

資源管理隔離

租戶之間的數據隔離

讓每個租戶應用擁有獨立的類靜態數據拷貝,這個目標主要通過修改getstatic/putstatic字節碼指令實現。下面是一個簡單的例子:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

每一個運行在Multi-tenant JVM上的程序都有不同System.out實例。就java.lnag.System內部實現來說,out是其類靜態變量:

public final class System {

    // The standard input, output, and error streams.
    // Typically, these are connected to the shell which
    // ran the Java program.
    /**
     * Default input stream
     */
    public static final InputStream in = null;
    /**
     * Default output stream
     */
    public static final PrintStream out = null;
    /**
     * Default error output stream
     */
    public static final PrintStream err = null;
……
}

Multi-tenant JVM對於標准的JVM行為進行的更改如下:

每一個租戶第一次使用java/lang/System時,都會觸發它的初始化,也就是<clinit>。而一般的JVM,java/lang/System只會被初始化一次。

<clinit>的執行,對於每一個靜態成員變量存取,都被重新定向到了具體的租戶存貯空間。比如對於out = null賦值,putstatic執行時實際上會找到當前的租戶,然後把值存到該租戶的空間去 ,getstatic有著類似的道理。

Java類庫支持多租戶語境

這部分主要通過改造類庫實現,具體的功能包括:

System.exit(code) 調用只會使當前租戶退出,而不會令整個JVM退出。而租戶申請的一些諸如File/Socket句柄之類系統資源,會隨著租戶的推出而被釋放。

租戶A不可能通過類似如下

ThreadGroup group = Thread.currentThread().getThreadGroup();

ThreadGroup parent = group.getParent();

枚舉線程的辦法獲得租戶B的線程。不同租戶的線程分屬於不同的線程組。

Java屬性值的隔離,比如同樣的語句System.getProperty("name")對於不同的租戶可能是不同的值。

資源管理隔離

這是Multi-tenant JVM很重要的功能。在Multi-tenant JVM上,Heap/CPU/Disk IO/Net IO這些資源的使用是受資源策略保護的,比如你可以去限制某個租戶它的CPU最少可以使用20%,而在系統空閒時,最大可以用到100%。

Multi-tenant JVM通過Token Bucket來對IO(Disk/Net)和CPU資源進行管理。對於IO而言,Multi-tenant JVM截獲IO有關的OS API調用,使得IO發生之前受制於我們預先規定的資源策略。我們舉個網絡IO的例子,例如Java程序從Socket的讀取操作,JDK內部的實現通過JNI實際上會對應到系統的API調用

ssize_t recv(int sockfd, void *buf, size_t len, int flags);    

在recv調用發生之前,Multi-tenant JVM通過資源策略保證租戶的IO使用帶寬不會超過給它設定的限制。關於網絡IO,我們這裡有一個很好的演示:

用簡單的-Xlimit:netIO=6M參數限制運行在Multi-tenant JVM上的Ftp Server帶寬上限讀寫各為6Mib/s。

關於對CPU管理,Multi-tenant JVM實現的基本的思路是,把租戶線程所花費的CPU時間量化為Tokens,運行時每一個租戶線程都會被周期性檢查是否其當前CPU時間的使用超過了給它設定的限制。如果超過,當前線程會被掛起,直到滿足限制為止。周期性檢查的代碼是由Multi-tenant JVM插入到租戶線程裡去的,對於用戶程序而言完全是透明的。

Multi-tenant JVM對於Heap的管理建立在Balanced GC Policy基礎之上。同一般的Java程序類似,你可以使用-Xms/-Xmx為租戶程序設定最大/最小的堆內存值。Balanced GC Policy基於Region對Heap進行管理,每個租戶程序根據-Xms/-Xmx的設定來為其分配Region,而租戶對象的分配也必然只能發生在它自己擁有的區域內。

多租戶JVM的用法與限制

IBM發布的Java 7 R1默認支持多租戶JVM,在命令行上添加-Xmt參數即可啟用。由於多租戶JVM對JVM的變更,JNI Native Libraries、JVMTI以及GUI programs在多租戶狀態下的使用是受限制的。Multi-tenant JVM並未實現對JNI的隔離,所以不同的租戶應用不能裝載依賴同樣的JNI Native Lib,所有發生在JNI Native Lib裡的IO,不會受限於該租戶資源消費策略。同樣的情況適用於CPU以及Memory。

Multi-tenant JVM目前沒有實現對JVMTI Agent的改造用以支持我們前面所描述的靜態數據的隔離,這可能會對用戶如果想調試Java核心類庫代碼(不是用戶代碼)造成困擾。

關於GUI,Multi-tenant JVM沒有實現底層對於UI程序消息隊列的隔離,所以不支持在同一個Multi-tenant JVM運行大於1個的GUI程序。

還有一點,不要在非Daemon線程裡寫 “暴力”的死循環代碼,例如:

while(true)
{  
try () {
    ....
} catch(Throwable t) {
{
}

}

最後需要注意的是,當開啟IO資源控制時,盡量一次寫出更多的字節,避免影響程序的IO性能。

總結

Multi-tenant JVM目前在應用啟動時間和更小的內存占用開銷方面已經被證實有效。根據目前的一些基准測試結果來看,對於簡單的應用,相較於一般JVM,Multi-tenant JVM可以獲得5~6倍的運行個數。後續計劃發布的版本仍然會集中在提高啟動時間、更小的內存開銷這兩個方面,也會陸續有一些性能的報告發布。

長遠來看,Multi-tenant JVM會基於用戶、IBM產品線以及技術社區等的反饋,做進一步的提高,以及解決一些目前所存在的局限性,比如對於JNI隔離的支持,JVMTi的多租戶支持等等。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved