我有一个大约 1000 万个键的列表作为字符串列表 - list("xxx","yyx"......)。我想查询一个数据库以在循环中获取这些 10M 键的记录,因为它一次最多可以处理 1 M。数据库返回一个记录数据集。所以我想单独调用 1M 个键,最后将所有的输出结合起来。
val list_len = list.length
var start = 0
val batch_size = 1000000 // 1-Milliom
val result = spark.createDataFrame(spark.sparkContext.emptyRDD[(Array[Byte])])
while (start < list_len) {
var end = start + batch_size
if (end > list_len) {
end = list_len
}
val df = spark.context.read("some-database")
.load.where('key' isin list.slice(start, end))
result = result.union(df)
// tried performing action and caching to prevent recursive
// result.count
// result.cache
start = start + batch_size
}
但这不起作用。 “ result = result.union(df) ”被递归地引起。所以在第一次迭代中,它扫描了 1M 条记录,在第二次 2M 期间,依此类推。
还有,
我想立即“执行”每次迭代而不是使用惰性求值,因为这样所有调用都同时对 db 进行,db 响应变慢。
答案 0 :(得分:0)
您所做的是在驱动程序中调用数据库,创建 n
数据帧。所以你可以用简单的scala来做,然后使用union减少子结果列表:
val list = (1 to 100) // what ever
val batchSize = 3 // 1M
val grouped = list.grouped(batchSize).toList // split list into sublist
val empty = spark.createDataFrame(spark.sparkContext.emptyRDD[(List[Byte])])
// for each group, call DB
val result = grouped.map{ subList =>
// random code, call the DB instead
subList.map(x => (x-1 to x+1).map(_.toByte)).toDF("data")
}.foldLeft(empty)(_ union _) // merge all sub-result DF into one
迭代执行读取是另一个问题,但这是我所知道的:默认情况下,第一个 spark 总是惰性的,直到调用一个操作(计数,写入,...)。这是因为一旦spark开始处理数据,预计会太大而无法保存在内存中,它需要将中间数据存储在磁盘上。 Read 可能是一个例外,因为它需要知道数据集模式。但是,所做的事情取决于用于访问数据的 Spark 连接器(例如,对于 csv 和 parquet,我相信如果您提供架构并且仅读取文件标题,则它很懒惰)
因此您可以查看用于访问数据库的 spark 连接器,也许您可以在调用 read 时立即触发调用。但这不太可能,因为我认为这违反了火花原则。
否则,您需要使用操作触发火花调用,并使用缓存保留中间结果。类似的东西:
val result = grouped.map{ subList =>
val subResult = spark.read.load.where(..., subList).cache
subResult.count // minimal action to trigger read and cache
subResult
}.foldLeft(empty)(_ union _) // merge all sub-result DF into one
注意:w.r.t 问题中的代码,请确保 .cache
位于 subResult
变量中,否则将不适用:
val subResult = spark.[...]
subResult.cache // does nothing, cache is not an action
subResult.count // subResult doesn't contain the cache in its execution dag
grouped.foreach{ subList =>
// read and write in parquet
spark.read.load.where(..., subList)
.write.mode("append").parquet("hdfs://some/path")
}
// you can later read the whole (h)dfs dataset:
val everything = spark.read.parquet("hdfs://some/path")
在上述所有情况下,调用的分布将取决于 grouped
和 map
或 foreach
运算符的类型。对于 List
它将是迭代的,一个接一个调用。如果你想要更多的控制,你可以研究 scala 并行映射执行,也许使用 Future 和 ExecutionContext。