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()
}
}
答案 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)
因此,您需要在作业ThreadLocal
或class
的顶层定义object
,然后致电df.get
以获取您的RDD中的SimpleDateFormat
操作
请参阅: