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:存储数据项到网格中

   
   
   
   
InputStream in = new FileInputStream( " /home/bela/bond.iso " );
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[]缓冲值。构造区块名称的时候会在全路径后面跟上“.#”。例如“/home/bela/bond.iso.#125”。

  每个区块大小为4000 bytes,当写入一个14K的文件“/home/bela/tmp.txt”,将产生如下区块:

  1. /home/bela/tmp.txt.#0 (4000 bytes) [0 - 3999]
  2. /home/bela/tmp.txt.#1 (4000 bytes) [4000 - 7999]
  3. /home/bela/tmp.txt.#2 (4000 bytes) [8000 - 11999]
  4. /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 类的主要方法

   
   
   
   
public GridFilesystem(Cache < String, byte [] > data, Cache < String,
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, byte [] > data;
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()方法的例子

   
   
   
   
// create directories
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()方法的例子

   
   
   
   
InputStream in = new FileInputStream( " /home/bela/bond.iso " );
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()方法的例子

   
   
   
   
InputStream in = in .getInput( " /home/bela/bond.iso " );
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[]等,一般指内存区域。

你可能感兴趣的:(内存,网络,文件系统)