如何使用scala将postgreSQL数据库连接到Apache Spark?

时间:2014-07-23 17:15:35

标签: scala apache-spark psql

我想知道如何在scala中执行以下操作?

  1. 使用Spark scala连接到postgreSQL数据库。
  2. 编写SELECT,UPDATE等SQL查询来修改表格 那个数据库。
  3. 我知道使用scala但是如何在打包时将psql scala的连接器jar导入到sbt中?

1 个答案:

答案 0 :(得分:43)

我们的目标是从Spark工作者运行并行SQL查询。

构建设置

将连接符和JDBC添加到libraryDependencies中的build.sbt。我只用MySQL试过这个,所以我会在我的例子中使用它,但Postgres应该是一样的。

libraryDependencies ++= Seq(
  jdbc,
  "mysql" % "mysql-connector-java" % "5.1.29",
  "org.apache.spark" %% "spark-core" % "1.0.1",
  // etc
)

代码

创建SparkContext时,告诉它要将哪些罐子复制到执行者身上。包括连接器jar。这是一个好看的方式:

val classes = Seq(
  getClass,                   // To get the jar with our own code.
  classOf[mysql.jdbc.Driver]  // To get the connector.
)
val jars = classes.map(_.getProtectionDomain().getCodeSource().getLocation().getPath())
val conf = new SparkConf().setJars(jars)

现在Spark已准备好连接到数据库。每个执行程序都将运行部分查询,以便结果可以进行分布式计算。

这有两种选择。较旧的方法是使用org.apache.spark.rdd.JdbcRDD

val rdd = new org.apache.spark.rdd.JdbcRDD(
  sc,
  () => {
    sql.DriverManager.getConnection("jdbc:mysql://mysql.example.com/?user=batman&password=alfred")
  },
  "SELECT * FROM BOOKS WHERE ? <= KEY AND KEY <= ?",
  0, 1000, 10,
  row => row.getString("BOOK_TITLE")
)

查看参数文档。简言之:

  • 您拥有SparkContext
  • 然后是创建连接的函数。这将在每个worker上调用以连接到数据库。
  • 然后是SQL查询。这必须与示例类似,并包含起始键和结束键的占位符。
  • 然后指定键的范围(在我的示例中为0到1000)和分区数。范围将在分区之间划分。因此,一个执行程序线程将最终在示例中执行SELECT * FROM FOO WHERE 0 <= KEY AND KEY <= 100
  • 最后我们有一个将ResultSet转换成某个东西的函数。在示例中,我们将其转换为String,因此您最终得到RDD[String]

自Apache Spark 1.3.0版以来,另一种方法可通过DataFrame API获得。您可以创建org.apache.spark.sql.DataFrame

而不是JdbcRDD
val df = sqlContext.load("jdbc", Map(
  "url" -> "jdbc:mysql://mysql.example.com/?user=batman&password=alfred",
  "dbtable" -> "BOOKS"))

有关选项的完整列表,请参阅https://spark.apache.org/docs/1.3.1/sql-programming-guide.html#jdbc-to-other-databases(可以像JdbcRDD一样设置密钥范围和分区数。)

更新

JdbcRDD不支持更新。但您可以在foreachPartition中完成这些操作。

rdd.foreachPartition { it =>
  val conn = sql.DriverManager.getConnection("jdbc:mysql://mysql.example.com/?user=batman&password=alfred")
  val del = conn.prepareStatement("DELETE FROM BOOKS WHERE BOOK_TITLE = ?")
  for (bookTitle <- it) {
    del.setString(1, bookTitle)
    del.executeUpdate
  }
}

(这会为每个分区创建一个连接。如果这是一个问题,请使用连接池!)

DataFrame通过createJDBCTableinsertIntoJDBC方法支持更新。