SparkStreaming 输出算子--foreachRDD
在使用SparkStreaming时不可避免的要将处理过后的的数据输出到外部数据源,如redis,mysql,hbase等数据库中。而连接这些外部数据源时需要一些连接对象(如jdbc连接mysql),因而在使用foreachRDD时常出现以下情况:
dstream.foreachRDD { rdd =>
val connection = createNewConnection() // 在驱动器(driver)进程执行
rdd.foreach { record =>
connection.send(record) // 将在worker节点上执行
}
}
但是上述写入数据库的代码是错误的。因为它需要把连接对象connection序列化,再从驱动器节点发送到worker节点。而这些连接对象通常都是不能跨节点(机器)传递的。
连接对象通常都不能序列化,或者在另一个进程中反序列化后再次初始化(连接对象通常都需要初始化,因此从驱动节点发到worker节点后可能需要重新初始化)。
为了解决上面代码的问题,需要在worker节点上创建连接对象,如:
dstream.foreachRDD { rdd =>
rdd.foreach { record =>
val connection = createNewConnection()
connection.send(record)
connection.close()
}
}
但是每次向数据发送数据都创建一个连接对象是非常消耗资源的,会大大减小系统的吞吐量。
因而,一个比较好的解决方案是使用 rdd.foreachPartition – 为RDD的每个分区创建一个单独的连接对象。
dstream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
val connection = createNewConnection()
partitionOfRecords.foreach(record => connection.send(record))
connection.close()
}
}
foreachPartition 将连接对象的创建开销就摊到很多条记录上,减少了资源的消耗。
wordCount代码如下:
object SaveasMysqlApp {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("SaveasMysqlApp")
val ssc = new StreamingContext(conf,Seconds(5))
val lines = ssc.socketTextStream("localhost",6789)
val result = lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
result.foreachRDD( rdd => {
rdd.foreachPartition{ PartitionRecord =>
val connection = createConnection() // 这行在驱动器(driver)进程执行
PartitionRecord.foreach(record=>{
val sql = "insert into wordcount(word,wordcount) values('" +record._1+ "'," + record._2 + ")"
connection.createStatement().execute(sql)
})
connection.close()
}
})
result.print()
ssc.start()
ssc.awaitTermination()
}
def createConnection() = {
Class.forName("com.mysql.jdbc.Driver")
DriverManager.getConnection("jdbc:mysql://localhost:3306/sparkstream","root","hadoop")
}
}
而最好的办法是在多个RDD批次之间也复用连接对象。可以创建一个静态连接池,在需要连接的时候从连接池中取出连接,使用结束后放回连接池。示例如下:
dstream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
// ConnectionPool 是一个静态的、懒惰初始化的连接池
val connection = ConnectionPool.getConnection()
partitionOfRecords.foreach(record => connection.send(record))
ConnectionPool.returnConnection(connection) // 将连接返还给连接池,以便后续复用之
}
}

浙公网安备 33010602011771号