假设我们在HDFS中有很多JSON-s,但是对于原型,我们将一些JSON-s本地加载到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
答案 0 :(得分:1)
不是答案;只是为了指出正确的方向。你得到&#34;任务不可序列化&#34;异常,因为您的val buf = new StringBuilder
在RDD&{39} foreach
(for(i <- eachJson)
)中使用。 Spark无法分发您的buf
变量,因为StringBuilder
本身不可序列化。此外,你不应该直接访问可变状态。因此,建议您将所需的所有数据都放到Accumulator
,而不仅仅是尺寸:
case class MyAccumulator(size: Int, result: String)
并使用rdd.aggregate
或rdd.fold
:
eachJson.fold(MyAccumulator(0, ""))(...)
//or
eachJson.fold(List.empty[MyAccumulator])(...)
或者只需将scanLeft
与collect
一起使用。
请注意,这不可扩展(与StringBuilder
/ collect
解决方案相同)。为了使其可扩展 - 使用mapPartitions
。
更新。 mapPartitions将为您提供部分聚合JSON的能力,因为您将获得&#34; local&#34; iterator(partition)作为输入 - 您可以将其作为常规scala集合进行操作。如果你没有连接一些小百分比的JSON,那就足够了。
eachJson.mapPartitions{ localCollection =>
... //compression logic here
}
答案 1 :(得分:1)
Spark的编程模型对于你想要达到的目标并不理想,如果我们采用&#34;聚合元素的一般问题取决于只能通过检查前面的元素来识别的东西&#34;,有两个原因:
所以它不是一个可能的问题(它是),而是一个问题&#34;它的成本是多少&#34; (CPU /内存/时间),它为你买的东西。
如果我要拍摄一个精确的解决方案(确切地说,我的意思是:保留元素顺序,例如由JSON中的时间戳定义,并将确切连续的输入分组到接近边界的最大量),我会:
sortBy
函数,这样做):这是一个完整的数据随机播放,所以它很昂贵。 其中一个关键是不要在步骤4和步骤5之间应用任何与分区混淆的东西。 只要&#34;分区映射&#34;适合驾驶员的记忆,这几乎是一个实用的解决方案,但成本非常高。
如果组不能达到最佳大小,那么解决方案变得更加简单(并且如果你已经设置了一个,它会尊重RDD的顺序):如果没有,你几乎可以编码Spark,只是一个JSON文件的迭代器。
Personnaly,我定义了一个递归累加器函数(没有任何火花相关的)就像这样(我想你可以使用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已经排序,则可以预测,否则可能无法预测),其总长度低于阈值。
可能是什么&#34;次优&#34;就是说,在每个分区的末尾,可能只有几个(可能是单个)JSON的Seq[String]
,而在下一个分区的开头,创建了一个完整的JSON。