多线程提高spark streaming数据写入到数据库

多线程提高spark streaming数据写入到数据库

需求

集群环境资源有限,需要跑多个spark streaming任务,每个任务必须占据1核,cpu利用率很低,需要对数据进行实时统计更新到数据库mysql给业务实时展示,数据聚合程度较低每批数据对数据库交互过多,正常提交submit提交使用一个核只能单线程操作数据库,数据高峰会出现延迟现象,如何不增加资源情况提高效率?

Spark Streaming foreachRDD以及foreachPartition 操作数据库连接写入数据

当资源富裕,正常使用foreachPartition实现数据遍历入库代码如下

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    val connection = createNewConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    connection.close()
  }
}

这里我们使用了springboot对数据库操作的插件,foreachRDD是在driver端执行的,而foreach是在worker端执行的引用的类要在foreachPartition 里面实例化。我们知道我们在提交代码的时候,提交这个动作是在driver端执行的。springboot的类要用注解的方式实例化,使用foreachPartition会报错无法序列化,所以这里我们没有用foreachPartition,例如下面代码

val stateDstream = words_course.reduceByKey(_ + _)
   stateDstream.foreachRDD(rdd => {
     val list = rdd.collect
     for (r <- list) {
       courseStudyMincCount.studyCourse(r._1,r._2)
     }
   })

但是上述代码方式只能是单线程的,效率很低,这里我们跳出spark-Streaming自己实现多线程数据库交互

foreachRDD内自定义多线程

代码如下

    stateDstream.foreachRDD(rdd => {
      val list = rdd.collect
      //300是每批线程数据量,根据数据总量动态设定总线程数
      val num = (list.length/300)+1
      var count = 0
      var list_num = 0
      val array: Array[util.ArrayList[Map[String, Int]]] = new Array[util.ArrayList[Map[String, Int]]](num)
      array(list_num) = new util.ArrayList[Map[String, Int]]
      val threadPool: ExecutorService = Executors.newFixedThreadPool(num)
      val fList: Array[FutureTask[String]] = new Array[FutureTask[String]](num)
      //切分数据到不同的list
      for (r <- list) {
        count = count + 1
        val map = Map(r._1 -> r._2)
        array(list_num).add(map)
        if (count % 300 == 0) {
          list_num = list_num+1
          array(list_num) = new util.ArrayList[Map[String, Int]]
        }
      }
      //将不同list的数据扔到不同线程去处理
      for(i <- 0 to num-1) {
        val f = new FutureTask[String](new Callable[String] {
          override def call(): String = {
            val bizLog = array(i)
            for(s <- bizLog){
              for ((key , value) <- s){
                courseStudyMincCount.studyCourse(key, value,i)
              }
            }
            return "work finished at " + Thread.currentThread().getId()
          }
        })
        fList(i) = f
        threadPool.execute(f)
      }
      for(i <- 0 to num-1) {
        println(fList(i).get())
      }
      threadPool.shutdown()
    })

注意点
1.最后需要executors.shutdown()。
如果是executors.shutdownNow()会发生未执行完的task强制关闭线程。
如果使用executors.awaitTermination()则会发生阻塞,不是我们想要的结果。
如果没有这个shutdowm操作,程序会正常执行,但是长时间会产生大量无用的线程池,因为每次foreachRDD都会创建一个线程池。

2.可不可以将创建线程池放到foreachRDD外面?
不可以,这个关系到对于scala闭包到理解,经测试,第一次或者前几次batch是正常的,后面的batch无线程可用。

3.线程池executor崩溃了就会导致数据丢失
原则上是这样的,但是正常的代码一般不会发生executor崩溃。至少我在使用的时候没遇到过。

你可能感兴趣的:(spark)