如何让Spark Streaming写出它的输出,以便Impala可以读取它?

时间:2014-06-13 11:51:16

标签: hadoop streaming hive apache-spark impala

我对Spark Streaming API有以下问题。我目前正在通过Flume将输入数据传输到Spark Streaming,我打算用它来对数据进行一些预处理。然后,我想将数据保存到Hadoop的文件系统并使用Impala进行查询。但是,Spark正在将数据文件写入单独的目录,并为每个RDD生成一个新目录。

这是一个问题,因为首先,Impala中的外部表无法检测子目录,只能检测它们指向的目录中的文件,除非进行分区。其次,Spark新添加的目录如此之快,以至于在Impala中为每个生成的目录定期创建一个新分区会非常糟糕。另一方面,如果我选择增加Spark中写入的滚动间隔,以便不经常生成目录,则会有一个额外的延迟,直到Impala可以读取传入的数据。这是不可接受的,因为我的系统必须支持实时应用程序。在Hive中,我可以使用以下设置配置外部表以检测子目录而无需分区:

set hive.mapred.supports.subdirectories=true;
set mapred.input.dir.recursive=true;

但据我所知,Impala没有这样的功能。

我目前正在使用以下代码从Flume读取数据并将其写入HDFS:

val stream = FlumeUtils.createStream(ssc, host, port, StorageLevel.MEMORY_ONLY_SER_2)
stream.map(event => new String(event.event.getBody().array(), Charset.forName("UTF-8"))).saveAsTextFiles(path)

此处,变量路径确定目录的前缀,文本文件(part-0000等)添加到该目录的前缀,目录名的其余部分是Spark生成的时间戳。我可以将代码更改为:

val stream = FlumeUtils.createStream(ssc, host, port, StorageLevel.MEMORY_ONLY_SER_2)
val mapStream = stream.map(event => new String(event.event.getBody().array(), Charset.forName("UTF-8")))
mapStream.foreachRDD(rdd => rdd.saveAsTextFile(path))

在这种情况下,文件将被添加到由path确定的同一目录中,但由于它们总是被命名为part-00000,part-00001,part-00002等,因此先前生成的文件将被覆盖。在检查Spark的源代码时,我注意到文件的名称由SparkHadoopWriter的open()方法中的一行确定:

val outputName = "part-"  + numfmt.format(splitID)

在我看来,没有办法通过Spark API操纵splitID。总而言之,我的问题如下:

  • 是否有任何方法可以使Impala中的外部表检测子目录?
  • 如果没有,是否有任何方法可以让Spark将其输出文件写入单个目录,或者以Impala可以立即读取的形式?
  • 如果没有,是否有任何类型的更新预期与Spark解决此问题或我应该只分支我自己的Spark版本,我可以决定它自己编写的文件的名称?

1 个答案:

答案 0 :(得分:1)

我不能代表Impala。

part-xxxxx是Spark遵循的hadoop约定。大多数工具都了解这种格式,我猜想Spark不能做太多。部件文件必须是唯一的,并且在文件名中附加分区号是一种常用技术。

我会在Impala中查看如何阅读零件文件,因为大多数hadoop工具都是这样生成的。

如果想要自定义目录结构 - 尽管这不是您的问题 - 可以轻松实现,例如更改prefix-timestamp-suffix格式。 Spark Steaming使用引擎盖下的Spark RDD.saveAsTextFiles(..),可以自定义。以下是DStream.scala的代码:

  def saveAsTextFiles(prefix: String, suffix: String = "") {
    val saveFunc = (rdd: RDD[T], time: Time) => {
      val file = rddToFileName(prefix, suffix, time)
      rdd.saveAsTextFile(file)
    }
    this.foreachRDD(saveFunc)
  }