SPARK在map / mapPartitions上下文中初始化数据库连接的成本

时间:2018-04-30 19:47:15

标签: apache-spark database-connection

借鉴互联网的例子,感谢那些有更好见解的人。

以下内容可以在与mapPartitions和map相关的各种论坛中找到:

... Consider the case of Initializing a database. If we are using map() or 
foreach(), the number of times we would need to initialize will be equal to 
the no of elements in RDD. Whereas if we use mapPartitions(), the no of times 
we would need to initialize would be equal to number of Partitions ...

然后有这样的回应:

val newRd = myRdd.mapPartitions(
  partition => {

    val connection = new DbConnection /*creates a db connection per partition*/

    val newPartition = partition.map(
       record => {
         readMatchingFromDB(record, connection)
     })
    connection.close()
    newPartition
  })

所以,我的问题是在阅读了与此有关的各种项目的讨论之后:

  1. 虽然我一般可以理解使用mapPartitions提高性能,但为什么根据第一段文本,每次使用map时RDD的每个元素都会调用数据库连接?我似乎无法找到正确的理由。
  2. sc.textFile不会发生同样的事情......并从jdbc连接读入数据帧。或者是吗?如果是这样,我会非常惊讶。
  3. 我错过了什么......?

2 个答案:

答案 0 :(得分:3)

首先,这段代码不正确。虽然它看起来像是established pattern for foreachPartition的改编版,但它不能像mapPartitions这样使用。

请记住,foreachPartition需要Iterator[_]并返回Iterator[_],其中Iterator.map是惰性的,因此此代码在实际使用之前关闭连接。

要使用在mapPartitions中初始化的某种形式的资源,您必须以某种方式设计代码,而不需要显式资源释放。

  

第一段文字,每次使用地图为RDD的每个元素调用数据库连接?我似乎无法找到正确的理由。

如果没有相关代码段,答案必须是通用的 - mapforeach不是为处理外部状态而设计的。如果您在问题中显示了API,则必须:

rdd.map(record => readMatchingFromDB(record, new DbConnection))

以明显的方式为每个元素创建连接。

使用单例连接池并不是不可能的,做类似的事情:

object Pool {
  lazy val pool = ???
}

rdd.map(record => readMatchingFromDB(record, pool.getConnection))

但要做到这一点并不总是那么容易(考虑线程安全性)。而且由于连接和类似对象不能一般地序列化,我们不能只使用闭包。

相比之下foreachPartition模式既明确又简单。

如果当然可以迫使急切执行使事情有效,例如:

val newRd = myRdd.mapPartitions(
  partition => {

    val connection = new DbConnection /*creates a db connection per partition*/

    val newPartition = partition.map(
       record => {
         readMatchingFromDB(record, connection)
    }).toList
    connection.close()
    newPartition.toIterator
  })

但它当然有风险,实际上可能会降低性能。

  

sc.textFile不会发生同样的事情......并从jdbc连接读取数据帧。或者是吗?

两者都使用更低的API进行操作,但当然没有为每条记录初始化资源。

答案 1 :(得分:0)

在我看来,连接应该在地图和关闭的任务完成之前保留一次并创建一次。

val connection = new DbConnection / 为每个分区创建数据库连接 /

val newRd = myRdd.mapPartitions(
  partition => {    

    val newPartition = partition.map(
       record => {
         readMatchingFromDB(record, connection)
     })

    newPartition
  })

connection.close()