SQARK 笔记(七) Task多线程

本文探讨了Spark Task在多线程环境下遇到的序列化问题及其原因,详细介绍了由于共享变量导致的线程不安全,并通过三个代码实现示例,包括线程不安全的单例工具类、在Driver端创建实例以及使用MapPartitions方法,展示了如何避免和解决这类问题,以确保任务执行的正确性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Task序列化问题

  问题原因 

一个Executor是一个进程,一个进程中可以同时运行多个Task,如果多个Task使用了共享的变量,就会出现线程不安全的问题

案例

需求

使用spark将日期字符串转换成long类型时间戳

样例数据

2019-11-06 15:59:50

2019-11-06 15:59:51

2019-11-06 15:59:52

2019-11-06 15:59:53

2019-11-06 15:59:54

2019-11-06 15:59:55

2019-11-06 15:59:56

2019-11-06 15:59:57

2019-11-06 15:59:58

 

代码实现一

多个Task使用一个单例的工具类,工具类使用共享的SimpleDateFormat会出现线程不安全的问题,所有方法要加锁,但是效率变低
 

object DateUtils {
  val sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  //为了避免多个线程同时使用一个SimpleDateFormat出现问题,加锁
  def parse1(line: String): Long = synchronized {
    val date = sdf1.parse(line)
    date.getTime
  }
  //FastDateFormat内部实现了锁机制
  val sdf2 = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")
  def parse2(line: String): Long = {
    val date = sdf2.parse(line)
    date.getTime
  }
}

 

object TaskThreadNotSafe01 {
  def main(args: Array[String]): Unit = {
    val isLocal = args(0).toBoolean
    //创建SparkConf,然后创建SparkContext
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
    if (isLocal) {
      conf.setMaster("local[*]")
    }
    val sc = new SparkContext(conf)
    val lines = sc.textFile(args(1))
    val timeRdd: RDD[Long] = lines.map(line => {
      val time: Long = DateUtils.parse2(line)
      time
    })
    //触发Action
    val r = timeRdd.collect()
    println(r.toBuffer)
    sc.stop()
  }
}

代码实现二

  1. 在driver端new一个类的实例,然后在函数中使用,一个Task有一个工具类的实例
  2. 工具类要实现序列化
class DateUtilsClass extends Serializable {
  val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  def parse(line: String): Long = {
    val date = sdf.parse(line)
    date.getTime
  }
}

object TaskThreadNotSafe02 {
  def main(args: Array[String]): Unit = {
    val isLocal = args(0).toBoolean
    //创建SparkConf,然后创建SparkContext
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
    if (isLocal) {
      conf.setMaster("local[*]")
    }
    val sc = new SparkContext(conf)
    //在Driver端被创建的【new一个类的实例】
    val dateUtils = new DateUtilsClass
    val lines = sc.textFile(args(1))
    val timeRdd: RDD[Long] = lines.map(line => {
      val time: Long = dateUtils.parse(line)
      time
    })
    //触发Action
    val r = timeRdd.collect()
    println(r.toBuffer)
    sc.stop()
  }
}

 

代码实现三

  1. 使用MapPartitions方法,工具类不用实现序列化
  2. 每一个Task都有一个但是的工具类实例
class DateUtilsClassNoSer {
  val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  def parse(line: String): Long = {
    val date = sdf.parse(line)
    date.getTime
  }
}

object TaskThreadNotSafe03 {
  def main(args: Array[String]): Unit = {
    val isLocal = args(0).toBoolean
    //创建SparkConf,然后创建SparkContext
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
    if (isLocal) {
      conf.setMaster("local[*]")
    }
    val sc = new SparkContext(conf)
    val lines = sc.textFile(args(1))
    val timeRdd = lines.mapPartitions(it => {
      //一个分区new一个DateUtilsClassNoSer工具类
      val dateUtils = new DateUtilsClassNoSer
      it.map(line => {
        dateUtils.parse(line)
      })
    })
    //触发Action
    val r = timeRdd.collect()
    println(r.toBuffer)
    sc.stop()
  }
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值