内容


Tachyon--Spark 缺省的 off-heap 内存框架

Comments

Tachyon 是什么

Tachyon 是 AMPLab 开发的一款内存分布式文件系统。它介于计算层和存储层之间,可以简单的理解为存储层在内存内的一个 Cache 系统。同 Spark 和 Hadoop 一样,Tachyon 是完全开源的,并且也是一个以 JVM 为 base 的系统。目前 Tachyon 的最新版本是 0.7.0,同时也在快速的开发新功能。Tachyon 和 Spark 都是出自于 AMPlab,所以不难想象为什么 Tachyon 成为 Spark 缺省的 off-heap 内存存储框架。

为什么会有 Tachyon

随着大数据的领域发展,应用层越来越在意计算层的延迟。这时候很多人发现,在存储层只要简单的以内存替换磁盘,就可以明显的减小延时。因此,国外很多社区都流行着一句话“Mem is King”,随之很多计算框架也开始向内存方向发展,如火如荼的 Spark 便是因此而生。以 Spark 为例,应用中可能会碰到如下的问题。

首先,Spark 的运行以 JVM 为基础,所以 Spark 的 Job 需要将数据存入 JVM 堆中。随着计算的迭代,JVM 堆中存放的数据会迅速增大。对于 Spark 而言,计算引擎跟存储引擎又处在同一个 JVM 中,所以会有重复的 GC 开销。这就响应了实时系统的低延时特性。其二,当 JVM 崩溃时,缓存在 JVM 堆中的数据也会丢失。这里 Spark 就不得不根据数据的 Lineage,重新计算丢失的数据。其三,当 Spark 需要跟 Hadoop 的 MapReduce 共享数据时,就必须通过第三方来共享,比如 HDFS。因为要借助第三方,就必须面对额外的开销,例如 HDFS 的 Disk IO。

随着基于内存计算框架的发展,以及以上的问题,促进了内存分布式文件系统的出现,也就是 Tachyon。对于 Spark 而言,计算数据存放于 Tachyon 之中,首先会减少 Spark 的 GC 开销。再而当 Spark 的 JVM 进程奔溃时,存放在 Tachyon 的数据不会受影响。如果 Spark 需要跟别的计算框架进行数据共享时,只要通过 Tachyon 的 Client 就可以做到了,并且延迟远低于 HDFS 等文件系统。

Tachyon 的安装

Tanchyon 有两种安装模式,一种是单机模式(Single Node),另一种则是集群模式(Gird)。我先以单机模式为例。我的系统是 Redhat6.6,Tachyon 的版本为 0.7.0。在安装 Tachyon 之前需要确认是否已经安装了 Java,并且版本不低于 Java6。我的 Java 版本为 Oracle 1.7.0。

单机版(Single Node)

首先,我们需要下载 Tachyon 的发行包。命令如下:

wget https://github.com/amplab/tachyon/releases/download/v0.7.0/tachyon-0.7.0-bin.tar.gz

拿到安装包之后,需要解压,并进入 Tachyon 的目录。命令如下:

tar xvfz tachyon-0.7.0-bin.tar.gz
cd tachyon-0.7.0

进入到 Tachyon 的目录之后,我们在 bin 的目录下面可以看到 Tachyon 的启动脚本。不过在执行启动脚本之前,我们需要配置一些基础的环境变量。Tachyon 启动时候会在 conf 目录寻找一个叫做 tachyon-env.sh 的文件,并将其中的配置传递给 Tachyon 的 JVM。在 Tachyon 的 conf 目录中, 会看到文件 tachyon-env.sh.template。这里只需简单的拷贝这个模板,并改名为 tachyon-env.sh 即可。命令如下:

cp conf/tachyon-env.sh.template conf/tachyon-env.sh

在上一章中,我们讲过 Tachyon 也需要将部分文件存储在持久化的文件系统。在 tachyon-env.sh 中有一项配置为 TACHYON_UNDERFS_ADDRESS,这一项就是用来配置 Tachyon 所使用的持久化文件系统。由于是单机模式,这里我们就可以使用本地的文件系统。我们需要配置一个本地的目录,一般配置在 tmp,因为这个目录的权限比较大。配置示例如下:

export TACHYON_UNDERFS_ADDRESS=/tmp

接下来,需要做一些 format 的操作,以及启动 Tachyon。在启动之前,我们需要配置 SSH 的无密码登录(我的系统已经配置了 root 用户的无密码登录)。如果没有配置,则启动过程中会要求输入用户的登录密码(跟 Hadoop 是类似的)。具体的命令如下。

./bin/tachyon format
./bin/tachyon-start.sh local

完成之后,就会在这台机器上启动 Tachyon 的 master 和 worker 进程。Tachyon 提供了简单的测试程序 runTest。我们可以通过 runTest 测试 Tachyon 是否正常。可以执行如下命令:

./bin/tachyon runTest Basic CACHE_THROUGH

执行完命令后,就可以看到如下图类似的输出。当出现 Passed the test,就代表测试 case 成功。这里先不纠结其内部整个工作流程。

图 1.runTest 执行结果
图 1.runTest 执行结果
图 1.runTest 执行结果

Tachyon 也提供了简单 WEB UI,默认端口为 19999。在 Tachyon 成功启动后,我们可以通过浏览器来查看 Tachyon 一些状态。如下图:

图 2.Tachyon 的 WEB UI
图 2.Tachyon 的 WEB UI
图 2.Tachyon 的 WEB UI

集群模式(Grid Mode)

集群模式和单机版只是一些配置的差异,安装包是一样的。因此关于下载和上一小节是完全一样的。对于集群模式,需要在每个机器的相同目录下解压 Tachyon 的安装包。方便起见,也需要配置 Tachyon Master 机器到所有 Worker 机器的 SSH 无密码登录服务。否则启动过程中,会一直要求输入用户的登录密码(跟 Hadoop 的启动是类似的)。我这里以三台机器 zwshen13,zwshen14,zwshen15 为例,zwshen13 为 Tachyon 的 Master,同时也是一台 Worker。zwshen14 和 zwshen15 都是 Tachyon 的 Worker 节点。

首先,我们在 zwshen13 上,进入 Tachyon 的 conf 目录,找到 workers 文件。需要在这个文件中以行为单位,写入 Worker 节点的机器名。我的配置如下:

[root@zwshen13 conf]# cat ./workers
# A Tachyon Worker will be started on each of the machines listed below.
zwshen13
zwshen14
zwshen15

配置完 workers 之后,我们需要登录到三台机器,为每一台机器创建 tachyon-env.sh,并在 tachyon-env.sh 中配置 TACHYON_MASTER_ADDRESS 为 zwshen13。Tachyon 提供了如下的命令,可以自动生成 tachyon-env.sh,并完成基本的配置。因此,我们只需登录到每台机器执行如下的命令:

./bin/tachyon bootstrap-conf zwshen13

完成以上的配置,我们就可以通过如下的命令启动 Tachyon 的集群。

./bin/tachyon format
./bin/tachyon-start.sh all Mount

启动成功后,我们就可以通过 WEB UI 查看到这三个 Worker 节点的状态(目前的版本中,WEB 会以 IP 地址代替主机名来显示)。如下图:

图 3.Tachyon 的 WEB UI 中 Worker 节点的状态
图 3.Tachyon 的 WEB UI 中 Worker 节点的状态
图 3.Tachyon 的 WEB UI 中 Worker 节点的状态

当集群启动成功后,我们也可以用 Tachyon 的 runTest 来测试 Tachyon 是否正常。我们刚才在启动 Tachyon 的集群时,没有在 tachyon-env.sh 中配置 TACHYON_UNDERFS_ADDRESS。所以 Tachyon 会在自己的 TACHYON_HOME 下建立 underFSStorage 目录存放持久化文件。但是目录权限很多时候会有问题。如果 runTest 触发文件权限相关的失败 case,则可以重新配置 TACHYON_UNDERFS_ADDRESS,并指向系统 tmp 即可。重新配置之后,需要再次执行 format,并重启 Tachyon。

实际应用中,Tachyon 很少用系统的文件系统来存放持久化文件,而是使用 HDFS、GlusterFS 或者 S3 等。以 HDFS 为例,Tachyon 默认使用 HDFS Client 为 1.0.4 的版本。如果需要连接其他版本的 HDFS,则需要重新编译 Tachyon 并且在 pom.xml 中指定具体的 HDFS 版本(也就是 Hadoop 的版本)。当安装的 Tachyon 和 HDFS 匹配时,只需要在 tachyon-env.sh 中指定 HDFS 的地址即可。然后重新 format,并重启 Tachyon。示例配置如下:

export TACHYON_UNDERFS_ADDRESS=hdfs://zwshen13:8020

Tachyon 的基本原理设计

Tachyon 的基本结构

Tachyon 也是一个 Master-Slave 结构的分布式系统。一个 Tachyon 的集群中会有一个 Master 节点及多个 Worker 节点,结构大致如下图。

图 4.Tachyon 的基本结构
图 4.Tachyon 的基本结构
图 4.Tachyon 的基本结构

当 Tachyon 的 Worker 节点启动之后,Worker 会向 Master 注册。注册成功之后,Master 会和 Worker 之间维护一个心跳。Tachyon 会将内存映射为 Block 设备,也就是 RamDisk。然后,Tachyon 会将数据存放于每个 Worker 的 Ramdisk,也就是内存中。Tachyon 可以以内存的速度去读写 Ramdisk 中存放的数据。Master 是存放媒体信息的,例如文件的大小,以及文件在哪个 Worker。这里的设计其实类似于 HDFS 得 NameNode 和 DataNode。Worker 会在心跳中将数据信息传递给 Master,Master 会更新数据的媒体信息。对于一个集群来说,内存总是有限的,因此 Tachyon 还需要支持硬盘的文件系统,这里称之为 UnderFS。Tachyon 会将一些持久性的文件存在 UnderFS 中。这里的 UnderFS 可以是 OS 的文件系统,也可以是 HDFS、GlusterFS、S3 之类的分布式文件系统。

Tachyon 的 HA

Tachyon 本身没有 HA 机制,它的 HA 是靠 Zookeeper 来实现的。大致如下图。

图 5.Tachyon 的 HA
图 5.Tachyon 的 HA
图 5.Tachyon 的 HA

我们通过 Zookeeper 可以在集群中启动多个 Master 节点,但是只有一个是真正在工作的,其他的 Master 节点都是 Standby 的状态。这里还有一个比较重要的点,就是 Tachyon 的 Master 需要将 Journal 信息放在 UnderFS 中,一般就是 HDFS、GlusterFS 之类的分布式文件系统。之所以这样做,是因为当主 Master 节点挂掉之后,Standby 的 Master 需要接管 Tachyon 集群,并且从 UnderFS 系统中读取到之前 Master 的 Journal 信息。否则,将无法获取到之前的 Master 状态,也就是之前 Master 中存放的媒体数据。个人认为,这里也可以直接通过 NFS 共享目录实现。但是没有文档说明。我目前也没有测试过 NFS。

数据的容错(Lineage)

Lineage 的概念,一开始应该是从 Spark 中引入的。大致也就是说,数据的元信息上会有数据的来源信息,以及依赖信息。如果这一数据在内存中消失(例如 JVM crash),那么 Spark 会根据数据的来源信息,以及依赖信息,重新计算出该丢失的数据。例如下图中,数据 A 和 B 计算出 C,C 和 D 计算出 E。那么当 C 数据丢失时,Spark 会重新用 A 和 B 计算出 C。Tachyon 只是将这一理念从计算层移动到存储层。从而让计算层更专心的去做计算,进而减少计算层的延迟。

图 6. 数据的 Lineage 容错
图 6. 数据的 Lineage 容错
图 6. 数据的 Lineage 容错

Tachyon 对数据的处理简介

我们先大概说下 Tachyon 是如何写文件的。当 Client 要向 Tachyon 写数据的时候,Client 首先会向本地 Worker 申请空间。当空间不足的时候,Worker 会根据最近的访问时间,将长时间没用的数据删除掉。Worker 分配好空间后会向 Client 返回。具体这里就是一个 Block 的信息,加上一个偏移量。Client 会向该 Block 写入信息。写入完成后,会通知 Woker 进行 Cache(数据已经在在内存中,只是移动目录)。Cache 有好几种不同的方式。在 Tachyon 的安装中,我们曾跑过一个 runTest 的测试用例。命令如下:

./bin/tachyon runTest Basic CACHE_THROUGH

这里的 CACHE_THROUGH 就是一种写入方式。执行 tachyon runTest 就可以看到所有的写入方式。Tachyon 总共有 5 种写入方式,分别是 MUST_CACHE、TRY_CACHE、CACHE_TRHOUGH、THROUGH、ASYNC_TRHOUGH。其实我们从字面含义,就可以猜出不少的区别。MUST_CACHE 要求 Tachyon 将数据必须写入内存,如果失败就会报错。TRY_CACHE 是将数据尽量写入内存,如果内存不够就会写入 UnderFS(底层文件系统或者 HDFS、S3 等)。CACHE_TROUGH、THROUGH 与 ASYNC_TRHOUGH 最终的结果都是将数据写入 UnderFS。区别是 CACHE_TROUGH 会在内存中也创建数据的副本,不过是最后会删除,而 ASYNC_THROUGH 是异步的写入 UnderFS,不保证一定能写入成功,其目的是为了减少延迟,提高响应时间。这些 Case 我们都可以通过 runTest 测试一下。然后可以从 Tachyon 的 GUI 中看看内存数据的变化。当然也可以通过 tachyon tfs 这个命令去查看(类似于 hadoop fs 命令)。

至于读文件,就稍微简单一些。Client 会先向 Master 请求所要读取数据的媒体信息,也就是 Block ID 和位置信息(例如哪个 Worker)。Client 拿到信息之后,会请求 Worker 去锁定该数据,然后读取文件,并更新文件的最新访问时间(写数据的时候,如果内存不够会根据这个时间,删除长时间不用的数据)。

其实 Spark 也会对 cache 的数据做 LRU(last recent used)处理。可见 Tachyon 的设计上借鉴了不少 Spark 的设计(毕竟出自同一个地方)。

在 Tachyon 之上运行 Spark

Tachyon 是 Spark 默认的 off-heap 内存方案,因此,Spark 的安装包已经 build-in 了 Tachyon 的 Client。不过 Spark 与 Tachyon 有一个默认的匹配关系如下图。

图 7.Spark 与 Tachyon 版本的匹配关系
图 7.Spark 与 Tachyon 版本的匹配关系
图 7.Spark 与 Tachyon 版本的匹配关系

我的环境中 Spark 为 1.4.0 的版本,我的 Tachyon 为 0.7.0 的版本,也是可以正常工作的。这里不需要什么额外的配置(也由此可见 Tachyon 与 Spark 的紧密关系)。以下我用 Spark-shell 做一些简单的示例。

首先,在本地创建一个 test 文件并保存到 Tachyon 中。执行如下的命令:

./bin/tachyon tfs copyFromLocal test tachyon://zwshen13:19998/test

成功将 test 放入 Tachyon 之后,在 SPARK_HOME 的 bin 目录,执行 spark-shell。如下:

./spark-shell

进入给 shell 之后,依次执行如下的 Scala 代码(执行过程中留意 count 的返回信息)。示例代码如下:

scala> val s = sc.textFile("tachyon://zwshen13:19998/test")
scala> s.count()
scala> s.saveAsTextFile("tachyon://zwshen13:19998/test_saved")

执行完如上的代码后,Spark 会在 Tachyon 中创建一个目录 test_saved,里面会存放 output 文件。output 文件中存放着与 test 一样的内容。不过这个 output 可能不是一个文件,而是多个(多个 partition 引起的)。

上面这个例子可以理解为将 Spark Job 的输出写入 Tachyon 中。这样其他的 Job 可以直接从 Tachyon 中读取。这里不局限于 Spark,例如 hadoop 也可以从 Tachyon 中读取 Spark 的输出。

对于 Spark 计算中的 RDD 而言,也是可以存储在 Tachyon 中。例如可以在 Spark-shell 中执行如下的 code。不过执行之前,我们首先要对 Spark 做一些配置。因为默认情况下 Spark 使用的 Tachyon URL 为 tachyon://localhost:19998。这里我们需要对这个做修改。在 SPARK_HOME/conf 底下找到 spark-defaults.conf,在其中加入一行配置如下:

spark.externalBlockStore.url tachyon://zwshen13:19998

Code 示例如下:

scala> val rdd = sc.textFile("tachyon://zwshen13:19998/test")
scala> rdd.persist(org.apache.spark.storage.StorageLevel.OFF_HEAP)
scala> rdd.count()

执行完成后(不要退出 spark-shell),可以在 Tachyon 的 GUI 中查看内存中的文件,有如下的中间文件生成。这些文件就是用来存放 Spark 的 RDD。当 Spark 的 Job 执行完,这些中间文件会被删除。这里只要我们退出 spark-shell,这些文件就会被删除。

图 8.RDD 中间文件
图 8.RDD 中间文件
图 8.RDD 中间文件

说到这里,我们其实需要回顾下我最开始在 Spark 基础上提到的问题。联想那三个问题,就可以体会下将 RDD 存放在 Tachyon 中的优点了。简单来说,第一放在 RDD 中的数据,是可以躲避掉 Spark 中 GC 的开销。其二,当 Spark 的 JVM 挂掉之后,RDD 数据不会丢失。第三,放在 Tachyon 中的数据可以很方便地与第三方框架共享。关于更多 Tachyon 与 Spark 的应用和配置,需要在两个社区查看更多的文档。

Tachyon 在大数据生态圈的位置

基于上一节,我们已经了解 Spark 与 Tachyon 的关系。但是 Tachyon 的志向却并不是单单的服务于 Spark。Tachyon 在整个大数据生态圈中,处在计算层与存储层之间,大致如下图所示。本文没有给出 Hadoop 与 Tachyon 的应用示例,但是经过简单的配置,Tachyon 也是完全可以支持 Hadoop MapReduce 的 workload。对于用户已经完成的 MapReduce 应用,不需要更改任何的 code,也可以直接运行在 Tachyon 中。

图 9.Tachyon 在大数据生态圈中的位置
图 9.Tachyon 在大数据生态圈中的位置
图 9.Tachyon 在大数据生态圈中的位置

Tachyon 的 CLI 接口

为了方便用户操作 Tachyon 中的文件,Tachyon 提供了很多易用的 CLI 接口。这些接口主要就是 Tachyon 脚本中的子命令。这里举一些简单的例子说明。

将本地的文件(t1)拷贝到 Tachyon 中,命令如下:

tachyon tfs copyFromLocal ./t1 /

查看 Tachyon 中的根,命令如下:

tachyon tfs ls /

查看 Tachyon 系统中文本文件(t1)的内容:

tachyon tfs cat /t1

在 Tachyon 中创建 testDir 目录:

tachyon tfs mkdir /testDir

这里大部分命令跟 Hadoop fs 的子命令非常类似,所以不难理解和学习。而且 tachyon tfs 会弹出帮助信息,有兴趣的读者可以尝试下更多的命令。

结束语

内存计算框架的发展以及带来的优势,已经成为一个不争的事实。随着数据以及应用种类越来越多,计算框架的发展也呈现出一定的多样性。未来数据中心的集群中也绝不会是一种单一的计算框架,因此针对多种框架如何更好的利用内存,便也会成为一个比较热门的课题,并且也会涉及到更多的技术难点,例如多租户。从行业应用上,目前 Tachyon 还不太成熟,国内外涉及的企业还不太多,但是其对内存计算框架的弥补,以及天生的 Spark 集成优势,都非常明显。在未来,相信 Tachyon 可以更好的与计算层以及存储层协同工作,促进大数据的发展。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=1012847
ArticleTitle=Tachyon--Spark 缺省的 off-heap 内存框架
publish-date=08102015