Infinispan's GridFileSystem--基于內存的網格文件系統
Infinispan是JBoss Cache緩存框架的后續項目,它是一個開源的數據網格平臺,用于訪問分布式狀態的群集節點。GridFileSystem(網格文件系統)是一個全新的實驗性的API,這些API使Infinispan后端的網格數據像文件系統一樣展示出來。這一系列API繼承了JDK的File,InputStream和OutputStream類,創建了相應的:GridFile,GridInputStream和GridOutputStream類。還有一個幫助類GridFilesystem,也被包含在這個框架里面。這些API在Infinispan 4.1.0版本中就可以使用了(從4.1.0.ALPHA2 版起)。
GridFilesystem包含兩個Infinispan緩存器:一個用于元數據緩存(通常是完全復制),另外一個是用于實際數據的緩存(通常是分布式)。前一個復制緩存器使每個節點在本地都有元數據信息,像列出文件列表之類的任務就不必使用RPC遠程過程調用了。后一個是分布式緩存器,當存儲空間的容量用光的時候,就需要一種可擴展的機制來存儲這些數據。所有的文件都被分塊,每個塊都存儲為一個緩存項。
在這篇文章里面我們關注的特性是Infinispan的分布式模式。該模式增加了“分布式”特性,這是一種基于哈希一致性的技術。JBossCache框架只支持“復制”模式(就是在群集里面的每一個節點都向其它節點復制所有的數據)。
完全復制技術可以很好的用于小型群集,或者是在每個節點的存儲數據量都相對較小的情況。在群集中,當每個節點都向其它節點復制數據的時候,每個節點的平均數據存儲容量都與這個群集的大小以及數據的容量有關。這種復制的優點在于它通常只在本地節點讀取數據,因為每個節點都擁有這些數據;另外,當群集中有新節點加入或者需要移除現存節點的時候,它也不需要重新進行負載均衡。
另一方面,當你需要快速訪問大型數據集合,并且又無法忍受從磁盤(譬如:數據庫)中檢索數據時,內存網格文件系統將是一種更好的解決方案。
在之前的文章中,我們討論了ReplCache,它使用了基于哈希一致性的分布式技術實現了一個網格數據容器。從某種程度上說,ReplCache就是Infinispan分布模式的原型。
在Infinispan中,不管有沒有冗余備份,數據都可以存儲在網格中。舉個例子,只有將Infinispan的配置項設置為distributed cache mode(分布緩存模式),numOwners(所有者數量)設置為1,數據D才會被存儲到網格中。在這種情況下,基于哈希一致性算法,Infinispan只會選擇一臺服務器節點來存儲數據D。如果我們設置numOwners為2,那么Infinispan就會選擇兩臺服務器來存儲數據D,以此類推。
Infinispan的優勢在于它提供了聚合的網格內存。例如,假設我們有5臺主機,每臺主機都有1GB的內存,然后我們將參數numOwners設置為1,那么我們就總共有5GB內存容量-這顯然降低了開支費用。即便使用冗余備份-比如,設置numOwners為2-我們的配置也有2.5GB的內存容量。
然而有一個問題:如果我們有很多1K大小的數據項,卻只有少量的200MB大小的數據項,這將造成了數據分布不均衡。一些服務器因為存儲那些200MB的數據項,差不多把內存堆消耗殆盡,而另外的服務器的內存有可能還沒有使用。
還有一個問題是,如果當前數據項的大小超過了所給的單個服務器的可用內存堆:譬如,當我們試圖存儲2GB的數據項時,操作就會失敗,因為數據項大于服務器節點的1GB內存堆,調用Infinispan的方法Cache.put(K,V),就會引起OutOfMemoryException(內存溢出)錯誤。
要解決這些問題,我們要將一個數據項分成chunks(區塊)并將它們分散存儲到群集的節點中。我們將一個區塊的大小設置為8K:如果我們把2GB的數據項分為8K大小的區塊,最終將在網格中得到250,000個8K的區塊。在網格中存儲250,000個相等的區塊,當然比存儲少量的200MB的數據項要更加均衡。
當然,我們不會加重應用程序員的負擔,他們既不用在寫入的時候把數據項切分成區塊,也不用在讀取的時候把區塊合并恢復為數據項。但是,這種方法仍需要將整個完整的數據項保存在內存中,這是不可行的。
為了克服這點,我們使用了流:一個流(輸入流或輸出流)只處理單個項的數據子集。例如,與其將2GB的數據項寫入網格,不如對輸入文件迭代讀取(譬如每次讀取50K),再將其每次讀取的少量數據寫入到網格中。這樣,不管什么時候,只需要占用50K內存容量就夠了。
現在應用程序員可以編寫代碼(偽代碼)將2GB的數據項存入網格中,如下面的列表1所示:
列表 1:存儲數據項到網格中
OutputStream out=getGridOutputStream("/home/bela/bond.iso");
byte[] buffer=new byte[50000];
int len;
while((len=in.read(buffer, 0, buffer.length))!= -1){
out.write(buffer, 0, len);
}
in.close();
out.close();
我們也可以便攜類似的代碼用來讀取網格中的數據。
使用流接口的好處是:
- 它只需要使用一個很小的緩沖區來讀寫數據。這要優于給Infinispan的put()/get()方法創建一個2GB的byte[] 緩沖區。
- 通過網格分布存儲數據將使群集更加均衡。使用良好的哈希一致性功能,所有的服務器節點存儲的數據容量都是差不多大小。
- 我們能夠存儲大型文件,即使文件容量大于單個節點的JVM內存堆。
- 應用程序員無需編寫區塊操作的相關代碼。
架構
這個網格文件系統不僅需要存儲區塊,還要存儲元數據信息,像目錄、文件名、文件大小、最后修改時間等。
因為元數據很關鍵,并且也很小,我們決定把它存儲在網格的所有節點中。因此,我們需要一個Infinispan緩存器來保存元數據,我們不用分布模式而用復制模式(在每個節點都完全拷貝元數據);我們還需要一個Infinispan緩存配置項,指定分布模式下的數據區塊所期望的numOwners參數值。
需要注意的是,Infinispan并不會創建兩個JGroups 堆和通道,而是讓同一個CacheManager(緩存管理器)創建的元數據緩存器和區塊緩存器實例共享單獨的JGroups通道。
元數據緩存器
元數據緩存器將目錄和文件的路徑名稱保存為鍵,將元數據保存為值(如文件長度、最后修改時間、標記該條目是文件還是目錄等)。
不管是創建一個目錄或文件,刪除一個文件,或者是寫入一個文件,元數據緩存器都要更新。例如,當寫入一個文件的時候,這個文件的長度和最后修改時間都要在元數據緩存器里面更新。
元數據緩存器也被用于導航,比如列出目錄“/home/bela/grid”下面的所有子目錄。因為每次修改的元數據都被完全復制到每個服務器節點中,所以讀取元數據通常只要在一個節點本地進行,而不需要網絡通訊。
區塊緩存器
區塊緩存器中會保存一個個單獨的數據塊。里面的鍵存儲的是區塊名稱,值存儲的是byte[]緩沖值。構造區塊名稱的時候會在全路徑后面跟上“.#<chunk number>”。例如“/home/bela/bond.iso.#125”。
每個區塊大小為4000 bytes,當寫入一個14K的文件“/home/bela/tmp.txt”,將產生如下區塊:
- /home/bela/tmp.txt.#0 (4000 bytes) [0 - 3999]
- /home/bela/tmp.txt.#1 (4000 bytes) [4000 - 7999]
- /home/bela/tmp.txt.#2 (4000 bytes) [8000 - 11999]
- /home/bela/tmp.txt.#3 (2000 bytes) [12000 - 13999]
當前區塊的計算與當前讀寫的指針以及區塊大小相關。例如,需要在7900位置讀取1000 bytes數據時,將從區塊#1讀取99bytes,再從區塊#2讀取901 bytes。
區塊名稱(“/home/bela/tmp.txt.#2”)會根據哈希一致性技術,來選擇并定位讀寫的服務器節點。
API
GridFileSystem框架由4個類實現:
- org.infinispan.io.GridFilesystem
- org.infinispan.io.GridFile (繼承自 java.io.File)
- org.infinispan.io.GridOutputStream (繼承自 java.io.OutputStream)
- org.infinispan.io.GridInputStream (繼承自 java.io.InputStream)
Infinispan系統的入口點是GridFilesystem類:它用于實例化GridFile, GridOutputStream 和GridInputStream類。
列表 2:GridFileSystem 類的主要方法
GridFile.Metadata> metadata, int default_chunk_size){
...
}
public File getFile(String pathname){
...
}
public OutputStream getOutput(String pathname) throws IOException {
...
}
public InputStream getInput(String pathname) throws FileNotFoundException {
...
}
GridFilesystem的構造器需要傳入兩個Infinispan緩存器,第一個參數為數據區塊緩存器(指完全創建并運行的構造),第二個參數為元數據緩存器。上面的第三個參數名稱default_chunk_size用于設置默認區塊大小值。
列表 3:創建GridFileSystem對象的代碼
Cache<String,GridFile.Metadata> metadata;
GridFilesystem fs;
data = cacheManager.getCache("distributed");
metadata = cacheManager.getCache("replicated");
data.start();
metadata.start();
fs = new GridFilesystem(data, metadata, 8000);
方法getFile()會獲取一個文件的句柄,從而列出這個文件下面的文件列表,新建文件或新建目錄。getFile()方法返回的是GridFile類,GridFile類繼承了java.io.File類,并重寫了File類的大部分方法(但不是全部方法)。列表4的代碼片段演示了如何使用getFile()方法。
列表 4.使用GridFileSystem類中 getFile()方法的例子
File file=fs.getFile("/home/bela/grid/config");
fs.mkdirs(); // creates directories /home/bela/grid/config
// List all files and directories under "/usr/local"
file=fs.getFile("/usr/local");
File[] files=file.listFiles();
// Create a new file
file=fs.getFile("/home/bela/grid/tmp.txt");
file.createNewFile();
方法getOutput()會返回GridOutputStream實例,它可以把數據寫入網格中。下面列表5的代碼,演示了如何從本地文件系統拷貝數據到網格系統中(省略了異常處理代碼):
列表 5:使用GridFileSystem類中 getOutput()方法的例子
OutputStream out=fs.getOutput("/home/bela/bond.iso"); // same name in the grid
byte[] buffer=new byte[20000];
int len;
while((len=in.read(buffer, 0, buffer.length))!= -1){
out.write(buffer, 0, len);
}
in.close();
out.close();
getInput()方法創建了一個GridInputStream實例,它可以從網格系統中讀取數據。下面的列表6演示了getInput()方法調用的例子。
列表 6:使用GridFileSystem類中 getInput()方法的例子
OutputStream out=new FileOutputStream("/home/bela/bond.iso");
// local name same as grid
byte[] buffer=new byte[20000];
int len;
while((len=in.read(buffer, 0, buffer.length))!= -1){
out.write(buffer, 0, len);
}
in.close();
out.close();
用WebDav展示網格文件系統
從4.1.0.ALPHA2版本起,Infinispan將附帶集成了WebDAV的演示包。這個演示包在網站的下載頁面提供,從最新發布的Infinispan分發包里面,我們可以找到已經編譯好的完整的WAR文件。將這個WAR文件部署到你最喜歡的servlet容器中,你就可以用WebDAV協議把文件系統加載上去。地址是 http://YOUR_HOST/infinispan-gridfs-webdav/
這里還有另外一個視頻演示,兩個JBoss AS(JBoss應用服務器)實例啟動并運行infinispan-gridfs-webdav的web示例應用程序,WebDAV實例作為遠程驅動加載。文件已經拷貝到infinispan系統中,當一個實例停止的時候,我們還可以從另外一個實例中繼續讀取文件,這也是高可用性的樣例。
可監測性
網格文件系統提供的可監測性支持,它通過Infinispan框架的基礎實現,包括JMX和/或JOPR或JON。
JMX
JMX的監測報告可以在兩個不同級別上啟用:緩存管理器和緩存器。緩存管理器級別管理了它創建的所有緩存器實例。緩存器級別的管理包含了每個緩存器實例生成的信息。
JOPR
另一種管理多個Infinispan實例的方法是使用JOPR,它是JBoss的企業管理解決方案。JOPR的代理和自動搜索功能可以同時監測緩存管理器和緩存器實例。使用JOPR,管理員可以用可視化的方式訪問一些重要的運行時參數或統計數據,同時也可以得知這些參數和統計數據是否超標或過低。
總結
本文討論了在網格中如何通過Infinispan的一套新的流API,來使用網格文件系統存儲大容量的文件。另外還有一些新的功能,用于將數據區塊化存儲到網格中,使用不同級別的冗余,以及在內存中處理超大型的數據集。
網格文件系統是一個原型,它并沒有實現所有的java.io.*包里面的功能,但是它當前已經包含了大部分重要的方法。欲了解更多信息,請參閱相關的Infinispan的API文檔。
參考
網格文件系統:http://community.jboss.org/wiki/GridFileSystem
ReplCache文檔:http://www.jgroups.org/javagroupsnew/docs/replcache.html
Infinspan主頁:http://www.infinispan.org
啟用分布模式:http://docs.jboss.org/infinispan/4.0/apidocs/config.html#ce_default_clustering
關于作者
Bela Ban在瑞士的蘇黎世大學完成了他的哲學博士學位。在IBM研究中心待了一段時間后,他到康奈爾大學研讀博士后。然后,他在加利福尼亞州圣何塞市的Fujitsu Network Communications公司從事NMS/EMS工作。2003年,他全職加入了JBoss的開源工作。Bela 在JBoss公司管理Clustering團隊并負責JGroups項目。Bela的興趣包括網絡協議,性能,群組通訊,越野跑,騎自行車和Beerathlon(注:一項有關啤酒的游戲)。當不寫代碼的時候,他喜歡和家人共度時光。
Manik Surtani是Infinispan caching項目的負責人。
查看英文原文:Infinispan's GridFileSystem - An In-Memory Grid File System
注:緩存器(Cache)指分布式節點的內存緩存器,類似與組件。緩沖(Buffer)一般指緩沖區byte[]等,一般指內存區域。