[英]Split RDD of JSON-s by size in Scala
假设我们在HDFS中有很多JSON,但是对于一个原型,我们使用以下代码在本地将一些JSON加载到Spark中:
val eachJson = sc.textFile("JSON_Folder/*.json")
我想编写一个通过eachJson
RDD [String]并计算每个JSON大小的作业。 然后将大小添加到累加器,并将相应的JSON添加到StringBuilder
。 但是,当串联的JSON-s的大小超过阈值时,我们便开始将其他JSON-s存储在新的StringBuilder
。
例如,如果我们有100个JSON-s,并且开始逐个计算它们的大小,我们会发现从第32个元素开始,串联的JSON-s的大小超过了阈值,那么我们仅将第一个组合在一起31个JSON-s。 之后,我们从第32个元素开始。
到目前为止,我设法做的工作是获取必须根据以下代码拆分RDD的索引:
eachJson.collect()
.map(_.getBytes("UTF-8").length)
.scanLeft(0){_ + _}
.takeWhile(_ < 20000) //threshold = 20000
.length-1
我也尝试过:
val accum = sc.accumulator(0, "My Accumulator")
val buf = new StringBuilder
while(accum.value < 20000)
{
for(i <- eachJson)
{
accum.add(i.getBytes("UTF-8").length)
buf ++= i
}
}
但是我收到以下错误: org.apache.spark.SparkException: Task not serializable
。
如何通过Scala在Spark中执行此操作? 我使用Spark 1.6.0和Scala 2.10.6
没有答案; 只是为了指出正确的方向。 您会收到“任务不可序列化”异常,因为在RDD的foreach
使用了val buf = new StringBuilder
( for(i <- eachJson)
)。 由于StringBuilder
本身不可序列化,因此Spark无法分发您的buf
变量。 此外,您不应该直接访问可变状态。 因此,建议将所需的所有数据放入Accumulator
,而不仅仅是大小:
case class MyAccumulator(size: Int, result: String)
并使用rdd.aggregate
或rdd.fold
类的东西:
eachJson.fold(MyAccumulator(0, ""))(...)
//or
eachJson.fold(List.empty[MyAccumulator])(...)
或在collect
将其与scanLeft
一起使用。
请注意,这将不可扩展(与StringBuilder
/ collect
解决方案相同)。 为了使其具有可伸缩性,请使用mapPartitions
。
更新。 mapPartitions使您能够部分聚合JSON,因为您将获得“本地”迭代器(分区)作为输入-您可以将其作为常规的scala集合进行操作。 如果您可以确定没有串联一些小的JSON,那可能就足够了。
eachJson.mapPartitions{ localCollection =>
... //compression logic here
}
如果我们采用“汇总元素取决于只能通过检查以前的元素才能知道的内容”这一普遍问题,Spark的编程模型对于您要实现的目标不是理想的,这有两个原因:
因此,这实际上不是一个可能的问题,而是“购买成本”(CPU /内存/时间)(“多少钱”)的问题。
如果我要寻求一个精确的解决方案(确切地说,我的意思是:保留元素顺序,例如由JSON中的时间戳定义,并将完全连续的输入分组为接近边界的最大数量),我将:
sortBy
函数可以做到这一点):这是一个完整的数据混洗,因此很昂贵。 关键之一是不要应用任何在步骤4和5之间混乱的分区。只要“分区图”适合驱动程序的内存,这几乎是一种实用的解决方案,但代价却很高。
如果组不能达到最佳大小是可以的,那么解决方案将变得更加简单(并且如果您设置了RDD,它会遵循RDD的顺序):如果根本没有Spark,则几乎可以编写代码,只是JSON文件的迭代器。
就个人而言,我将像这样定义一个递归累加器函数(与火花无关)(我想您可以使用takeWhile编写更短,更有效的版本):
/**
* Aggregate recursively the contents of an iterator into a Seq[Seq[]]
* @param remainingJSONs the remaining original JSON contents to be aggregated
* @param currentAccSize the size of the active accumulation
* @param currentAcc the current aggregation of json strings
* @param resultAccumulation the result of aggregated JSON strings
*/
@tailrec
def acc(remainingJSONs: Iterator[String], currentAccSize: Int, currentAcc: Seq[String], resultAccumulation: Seq[Seq[String]]): Seq[Seq[String]] = {
// IF there is nothing more in the current partition
if (remainingJSONs.isEmpty) {
// And were not in the process of acumulating
if (currentAccSize == 0)
// Then return what was accumulated before
resultAccumulation
else
// Return what was accumulated before, and what was in the process of being accumulated
resultAccumulation :+ currentAcc
} else {
// We still have JSON items to process
val itemToAggregate = remainingJSONs.next()
// Is this item too large for the current accumulation ?
if (currentAccSize + itemToAggregate.size > MAX_SIZE) {
// Finish the current aggregation, and proceed with a fresh one
acc(remainingJSONs, itemToAggregate.size, Seq(itemToAggregate), resultAccumulation :+ currentAcc)
} else {
// Accumulate the current item on top of the current aggregation
acc(remainingJSONs, currentAccSize + itemToAggregate.size, currentAcc :+ itemToAggregate, resultAccumulation)
}
}
}
不,您需要使用此累积代码,并使其针对spark数据帧的每个分区运行:
val jsonRDD = ...
val groupedJSONs = jsonRDD.mapPartitions(aPartition => {
acc(aPartition, 0, Seq(), Seq()).iterator
})
这会将您的RDD[String]
转换为RDD[Seq[String]]
,其中每个Seq[String]
由连续的RDD元素组成(如果对RDD进行了排序,这是可以预测的,否则可能无法预测),它们的总长度低于阈值。 所谓“次优”的意思是,在每个分区的末尾,可能存在一个Seq[String]
其中只有几个(可能是单个)JSON,而在下一个分区的开头,则是一个完整的创建。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.