Apache spark 使用线程统计Spark中的写入记录

Apache spark 使用线程统计Spark中的写入记录,apache-spark,listener,Apache Spark,Listener,我使用onTaskEndSpark listener获取写入文件的记录数,如下所示: import spark.implicits._ import org.apache.spark.sql._ import org.apache.spark.scheduler.{SparkListener, SparkListenerTaskEnd} var recordsWritten: Long = 0L val rowCountListener: SparkListener = new SparkL

我使用
onTaskEnd
Spark listener获取写入文件的记录数,如下所示:

import spark.implicits._
import org.apache.spark.sql._
import org.apache.spark.scheduler.{SparkListener, SparkListenerTaskEnd}

var recordsWritten: Long = 0L

val rowCountListener: SparkListener = new SparkListener() {
  override def onTaskEnd(taskEnd: SparkListenerTaskEnd) {
    synchronized {
      recordsWritten += taskEnd.taskMetrics.outputMetrics.recordsWritten
    }
  }
}

def rowCountOf(proc: => Unit): Long = {
  recordsWritten = 0L
  spark.sparkContext.addSparkListener(rowCountListener)
  try {
    proc
  } finally {
    spark.sparkContext.removeSparkListener(rowCountListener)
  }
  recordsWritten
}

val rc = rowCountOf { (1 to 100).toDF.write.csv(s"test.csv") }
println(rc)

=> 100
但是,尝试在线程中运行多个操作显然会中断:

Seq(1, 2, 3).par.foreach { i =>
  val rc = rowCountOf { (1 to 100).toDF.write.csv(s"test${i}.csv") }
  println(rc)
}

=> 600
=> 700
=> 750
我可以让每个线程声明自己的变量,但spark上下文仍然是共享的,我无法重新识别特定
SparkListenerTaskEnd
事件属于哪个线程。有什么办法使它起作用吗


(好的,也许我可以把它做成单独的spark作业。但它只是程序的一部分,所以为了简单起见,我更喜欢使用线程。在最坏的情况下,我只会串行执行它,或者忘记计数记录…)

有点老套,但你可以使用累加器作为过滤的副作用

val acc = spark.sparkContext.longAccumulator("write count")
df.filter { _ =>
  acc.add(1)
  true
}.write.csv(...)
println(s"rows written ${acc.count}")

如果我理解的话,在过滤器中使用时,我会冒重复计数的风险,以防一些执行器丢失,不是吗?可能是这样,但spark指标也在内部使用累加器实现,因此不确定它们是否保证准确@Kombajnzbożowy您是正确的。如果您需要精确的数量,累加器不会处理丢失的节点,并且可以使用重复的节点进行计数。因此,如果您需要精确的计数,
cache
count
可能是一种方法