同一个执行器中的Spark任务如何共享变量(使用SimpleDateFormat的NumberFormatException)?

时间:2015-03-23 12:24:02

标签: multithreading apache-spark simpledateformat rdd

spark文档说明了

  

默认情况下,当Spark在不同节点上并行运行一组函数时,它会将函数中使用的每个变量的副本发送给每个任务。

如果我创建了一个Java SimpleDateFormat并在RDD操作中使用它,我得到了一个异常NumberFormatException: multiple points

我知道SimpleDateFormat不是线程安全的。但正如spark docs所说,这个SimpleDateFormat对象被复制到每个任务中,所以不应该有多个线程访问这个对象。

我推测一个执行器中的所有任务共享相同的SimpleDateFormate对象,我是对的吗?


此程序打印相同的对象java.text.SimpleDateFormat@f82ede60

object NormalVariable {

  // create dateFormat here doesn't change
  // val dateFormat = new SimpleDateFormat("yyyy.MM.dd")

  def main(args: Array[String]) {

    val dateFormat = new SimpleDateFormat("yyyy.MM.dd")

    val conf = new SparkConf().setAppName("Spark Test").setMaster("local[*]")
    val spark = new SparkContext(conf)

    val dates = Array[String]("1999.09.09", "2000.09.09", "2001.09.09", "2002.09.09", "2003.09.09")

    println(dateFormat)

    val resultes = spark.parallelize(dates).map { i =>
      println(dateFormat)
      dateFormat.parse(i)
    }.collect()

    println(resultes.mkString(" "))
    spark.stop()
  }
}

1 个答案:

答案 0 :(得分:0)

如您所知,SimpleDateFormat不是线程安全的。

如果Spark每个执行器使用一个核心(--executor-cores 1),那么一切都应该正常工作。但是,只要为每个执行程序配置多个核心,您的代码现在就运行多线程,SimpleDateFormat会同时由多个Spark任务共享,并且可能会破坏数据并抛出各种异常。

要解决此问题,您可以使用与非Spark代码相同的方法之一,即ThreadLocal,这可确保您获得每个线程SimpleDateFormat的一个副本。

在Java中,这看起来像:

public class DateFormatTest {

  private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyyMMdd");
    }
  };

  public Date convert(String source) throws ParseException{
    Date d = df.get().parse(source);
    return d;
  }
}

和Scala中的等效代码的工作方式相同 - 此处显示为spark-shell会话:

import java.text.SimpleDateFormat

object SafeFormat extends ThreadLocal[SimpleDateFormat] {
  override def initialValue = {
    new SimpleDateFormat("yyyyMMdd HHmmss")
  }
}

sc.parallelize(Seq("20180319 162058")).map(SafeFormat.get.parse(_)).collect

    res6: Array[java.util.Date] = Array(Mon Mar 19 16:20:58 GMT 2018)

因此,您需要在作业ThreadLocalclass的顶层定义object,然后致电df.get以获取您的RDD中的SimpleDateFormat操作

请参阅: