SparkStreaming之foreachRDD的正确使用姿势
1、错误方式
假如connection不支持序列化,那么会报序列化相关的异常,因为connection在Driver端生成,一般的连接是不支持序列化的,需要被序列化之后传递到Executor端,所以这样尽量避免
dstream.foreachRDD { rdd =>
//创建一个连接
val connection = createNewConnection() // 代码在Driver端执行
rdd.foreach { record =>
connection.send(record) // 代码在Executor执行
}
}
2、小小改正
小小的改正就是将connection的创建在Executor端执行
dstream.foreachRDD { rdd =>
rdd.foreach { record =>
val connection = createNewConnection() //在Executor端执行
connection.send(record)
connection.close()
}
}
3、进一步改正
在2中,实际上是为每个RDD的每个元素都创建了一个连接,这样会增加不必要的资源创建、销毁开销,所以尽量一个分区创建一个connection
dstream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
val connection = createNewConnection()
partitionOfRecords.foreach(record => connection.send(record))
connection.close()
}
}
4、最终优化
虽然3有了进一步的节省资源开销,但是还是不尽人意,所以用一个连接池作为静态对象,来创建相应的连接
//请注意,应按需延迟创建池中的连接,如果一段时间不使用,则超时,这样可以最有效地将数据发送到外部系统
dstream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
// ConnectionPool is a static, lazily initialized pool of connections
val connection = ConnectionPool.getConnection()
partitionOfRecords.foreach(record => connection.send(record))
ConnectionPool.returnConnection(connection) // return to the pool for future reuse
}
}