写点什么

Infinispan's GridFileSystem-- 基于内存的网格文件系统

2010 年 8 月 23 日

简介

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 个类实现:

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[] 等,一般指内存区域。


感谢侯伯薇对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2010 年 8 月 23 日 00:003372

评论

发布
暂无评论
发现更多内容

如何让区块链技术能够更好赋能数字社会建设

CECBC区块链专委会

区块链 数字经济

设计模式之——JDK动态代理的源码分析

诸葛小猿

动态代理 cglib 代理模式 Proxy

应用开发基础之-并发编程

superman

图文讲解 AQS ,一起看看 AQS 的源码……(图文较长)

程序员小航

AQS jdk源码 源码阅读 java 并发

那些不可貌相的代码规范

双儿么么哒

代码质量

ARTS 打卡(20.07.20-20.07.26)

小王同学

【API进阶之路】帮公司省下20万调研费!如何巧用情感分析API实现用户偏好调研

华为云开发者社区

反馈 API 华为云 API Explorer平台 用户调研

关于 Bash 的 10 个常见误解

柴锋

bash Linux DevOps Shell

微服务、DDD

chenzt

ARTS打卡 第11周

引花眠

ARTS 打卡计划

计算机网络基础(十六)---传输层-可靠传输的基本原理

书旅

计算机网络 网络 协议族 网络层

深化区块链技术的应用 体现其价值产业发展良机

CECBC区块链专委会

区块链技术 数字经济

吃灰的旧显示器别扔!

Sicolas Flamel

学习 随笔杂谈

求刚好大于当前数组组合,Code Review最佳实践,JVM框架原理,JVM垃圾回收原理 John 易筋 ARTS 打卡 Week 12

John(易筋)

Code Review ARTS 打卡计划 JVM虚拟机原理 JVM垃圾回收原理 Array算法

热潮-区块链的价值能够体现在哪些方面?

CECBC区块链专委会

区块链技术 标准化 应用价值

十年一梦,小米的原罪得到救赎了吗?

脑极体

面试这么撩准拿offer,HashMap深度学习,扰动函数、负载因子、扩容拆分,原理和实践验证,让懂了就是真的懂!

小傅哥

Java 面试 hashmap 负载因子 扰动函数

十多位全球技术专家,为你献上近十个小时的.Net微服务介绍

newbe36524

微服务 .net core netcore 容器化

Suricata-流的处理

Phantasm

网络安全 suricata flow

BGP、OSPF、MPLS路由协议RFC分享

Phantasm

ARTS打卡Week 10

teoking

程序的机器级表示-控制

引花眠

计算机基础

学了那么多技术,为何依然成不了架构师

菜根老谭

架构设计原则

ARTS-WEEK10

一周思进

ARTS 打卡计划

《Java并发编程的艺术》读书笔记1:说说并发编程

Jason

多线程 并发

云图说 | 3分钟创建一个游戏类工作负载

华为云开发者社区

Docker 容器 华为云 工作负载 2048游戏

当实证资产定价遇上机器学习

分析101

人工智能 学习 金融科技 金融 资产定价

Java 常见的几种 OOM

hepingfly

Java OOM

一次好的聊天可以超过自己努力啃几周的书籍

良知犹存

程序人生

品质网络的迭变之路,以及运营商的未来之匙

脑极体

边云协同!EM-BOX视频分析盒加速安全生产场景落地AI应用

百度大脑

人工智能 人脸识别 图像识别 百度大脑 人体识别

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Infinispan's GridFileSystem--基于内存的网格文件系统-InfoQ