1、通过map关联外部数据源
val data: RDD[Int] = sc.parallelize(1 to 10, 2)
// 1、外关联、SQL查询
// 问题:每一条数据都要建立一个数据连接,执行SQL,然后关闭连接
val res01: RDD[String] = data.map(
(value: Int) => {
println("-----连接数据库-----")
println(s"-----select $value-----")
println("-----关闭数据库连接-----")
value + " selected"
}
)
res01.foreach(println)
问题:每一条数据都会对数据库建立连接,执行SQL语句,关闭连接。频繁的建立关闭连接会造成大量的资源消耗
2、通过mapPartitionsWithIndex和mapPartitions关联外部数据源
// mapPartitions 实现方式与以下方法相似
val res2: RDD[String] = data.mapPartitionsWithIndex(
(pindex, piter) => {
// 需要返回Iterator
val lb = new ListBuffer[String]
println(s"-----$pindex----连接数据库-----")
while (piter.hasNext) {
val value: Int = piter.next()
println(s"-----select $value-----")
lb += (value + " selected")
}
println("-----关闭数据库连接-----")
lb.iterator
}
)
res2.foreach(println)
解决问题:基于分区对数据库建立连接,执行SQL语句,然后关闭连接,减少了建立连接的次数,降低资源的消耗
问题:运算过程中引入了新的迭代器,在分区过多及数据量过大的情况下,会在内存中积压大量数据,势必造成OOM
3、通过mapPartitionsWithIndex和mapPartitions关联外部数据源-采用迭代器嵌套模式【优化点】
val res03: RDD[String] = data.mapPartitionsWithIndex(
(pindex, piter) => {
// 需要返回Iterator
// 迭代器嵌套,中间不出现积压内存情况
new Iterator[String] {
println(s"-----$pindex----连接数据库-----")
override def hasNext = if (piter.hasNext == false) {
println("-----关闭数据库连接-----")
false
} else true
override def next() = {
val value: Int = piter.next()
println(s"-----select $value-----")
value + " selected"
}
}
}
)
res03.foreach(println)
解决问题:利用迭代器可嵌套的特性,解决缓存中间结果可能导致OOM问题
4、总结
- spark在关联外部数据源时,建议使用mapPartitionsWithIndex和mapPartitions,而是不使用map,针对一整个分区操作,比针对每条数据操作快得多
- 在使用mapPartitions时,推荐使用迭代器嵌套模式,这样既不会存在中间缓冲结果,又可以减少不必要的资源消耗