我对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。总而言之,我的问题如下:
答案 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)
}