多线程提高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崩溃。至少我在使用的时候没遇到过。