将多线程计算密集型作业移植到spark

时间:2015-08-28 17:41:38

标签: java apache-spark

我正在研究使用执行器服务来并行化任务的代码(想想一遍又一遍地在小数据集上完成的机器学习计算)。 我的目标是尽可能快地执行一些代码,并将结果存储在某处(总执行时间至少为100M)。

逻辑看起来像这样(它是一个简化的例子):

dbconn = new dbconn() //This is reused by all threads
for a in listOfSize1000:
   for b in listofSize10:
      for c in listOfSize2:
         taskcompletionexecutorservice.submit(new runner(a, b, c, dbconn))

最后,调用taskcompletionexecutorservice.take()并将结果存储在" Future"在数据库中。 但是这种方法在一点之后并没有真正扩展。

所以这就是我现在正在做的事情(这是一个残酷的黑客,但我正在寻找关于如何最好地构建这个的建议):

sparkContext.parallelize(listOfSize1000).filter(a -> {
   dbconn = new dbconn() //Cannot init it outsize parallelize since its not serializable
   for b in listofSize10:
      for c in listOfSize2:
         Result r = new runner(a, b, c. dbconn))
         dbconn.store(r)

    return true //It serves no purpose.
}).count();

这种方法对我来说看起来效率低下,因为它并没有真正平行于最小的工作单元,尽管这项工作没有问题。对于我来说,计数并没有真正做任何事情,我添加它来触发执行。它的灵感来自于计算这里的pi示例:http://spark.apache.org/examples.html

所以有关如何更好地构建我的spark runner的任何建议,以便我可以有效地使用spark执行器?

2 个答案:

答案 0 :(得分:1)

因此我们可以做一些事情来使这个代码更像Spark。首先是您使用filtercount,但实际上使用的是其中之一。函数foreach可能更接近你想要的。

那就是说你正在创建一个数据库连接来存储结果,我们可以通过几种方式来看这个。一个是:如果数据库确实是您要用于存储的内容,则可以使用foreachPartitionmapPartitionsWithIndex为每个分区创建一个连接,然后执行count()(我知道)有点难看,但从1.0.0开始不推荐使用foreachWith。您也可以只做一个简单的map,然后将结果保存到许多支持的输出格式中(例如saveAsSequenceFile)。

答案 1 :(得分:1)

你可以尝试另一种方法来更好地并行化它,不过价格。代码在scala中,但是python有 cartesian 方法。为简单起见,我的列表包含整数。

val rdd1000 = sc.parallelize(list1000)
val rdd10 = sc.parallelize(list10)
val rdd2 = sc.parallelize(list2)

rdd1000.cartesian(rdd10).cartesian(rdd2)
    .foreachPartition((tuples: Iterator[Tuple2[Tuple2[Int, Int], Int]]) => {
        dbconn =...
        for (tuple <- tuples) {
            val a = tuple._1._1
            val b = tuple._1._2
            val c = tuple._2

            val r = new Result(a, b, c, dbconn)
            dbconn.store(r)
        }
    })
在您的情况下,

过滤器转换,这是懒惰的 - 在调用时spark不会对其进行评估。只有在调用操作时才会启动该过程。 收集操作,它会在您的示例中开始实际处理。 ForeachPartition 也是一个动作,火花马上启动它。此处需要 ForeachPartition ,因为它允许为整个数据分区打开一次连接。

对于笛卡儿来说,糟糕的事情可能是它可能意味着在群集上进行混乱,所以如果你有复杂的对象,那可能会影响性能。如果您要从外部源读取数据,可能会发生这种情况。如果你打算使用parallelize,那很好。

还有一点需要注意的是,根据群集的大小,火花可能会对您使用的数据库造成很大的压力。