前段时间用sparksession读取MySQL的一个表的时候,出现耗时长,频繁出现oom等情况,去网上查找了一下,是因为用的默认读取jdbc方式,单线程任务重,所以出现耗时长,oom等现象.这时候需要提高读取的并发度.现简单记录下.
看sparsession DataFrameReader源码,读取jdbc有三个方法重载.
def jdbc(url: String, table: String, properties: Properties): DataFrame
val url: String = "jdbc:mysql://localhost:3306/testdb"
val table = "students"
//连接参数
val properties: Properties = new Properties()
properties.setProperty("username","root")
properties.setProperty("password","123456")
properties.setProperty("driver","com.mysql.jdbc.Driver")
val tb_table: DataFrame = sparkSession.read.jdbc(url, table, properties)
查看并发度tb_table.rdd.getNumPartitions #返回结果1
该操作的并发度为1,你所有的数据都会在一个partition中进行操作,意味着无论你给的资源有多少,只有一个task会执行任务,执行效率可想而之,并且在稍微大点的表中进行操作分分钟就会OOM.
2. 根据Long类型字段分区
调用函数
def jdbc(
url: String,
table: String,
columnName: String, # 根据该字段分区,需要为整形,比如id等
lowerBound: Long, # 分区的下界
upperBound: Long, # 分区的上界
numPartitions: Int, # 分区的个数
connectionProperties: Properties): DataFrame
使用,校验
val url: String = "jdbc:mysql://localhost:3306/testdb"
val table = "students"
val colName: String = "id"
val lowerBound = 1
val upperBound = 10000
val numPartions = 10
val properties: Properties = new Properties()
properties.setProperty("username","root")
properties.setProperty("password","123456")
properties.setProperty("driver","com.mysql.jdbc.Driver")
val tb_table: DataFrame = sparkSession.read.jdbc(url, table,colName, lowerBound, upperBound, numPartions, properties)
查看并发度tb_table.rdd.getNumPartitions #返回结果10
该操作将字段 colName 中1-10000000条数据分到10个partition中,使用很方便,缺点也很明显,只能使用整形数据字段作为分区关键字
3. 根据任意类型字段分区
调用函数
jdbc(
url: String,
table: String,
predicates: Array[String],
connectionProperties: Properties): DataFrame
使用,校验
val url: String = "jdbc:mysql://localhost:3306/testdb"
val table = "students"
/**
* 将9月16-12月15三个月的数据取出,按时间分为6个partition
* 为了减少事例代码,这里的时间都是写死的
* sbirthday 为时间字段
*/
val predicates =
Array(
"2015-09-16" -> "2015-09-30",
"2015-10-01" -> "2015-10-15",
"2015-10-16" -> "2015-10-31",
"2015-11-01" -> "2015-11-14",
"2015-11-15" -> "2015-11-30",
"2015-12-01" -> "2015-12-15"
).map {
case (start, end) =>
s"cast(sbirthday as date) >= date '$start' " + s"AND cast(sbirthday as date) <= date '$end'"
}
val properties: Properties = new Properties()
properties.setProperty("username","root")
properties.setProperty("password","123456")
properties.setProperty("driver","com.mysql.jdbc.Driver")
val tb_table: DataFrame = sparkSession.read.jdbc(url, table,predicates, properties)
查看并发度 tb_table.rdd.getNumPartitions #结果为6
该操作的每个分区数据都由该段时间的分区组成,这种方式适合各种场景,较为推荐。
MySQL单partition,大表极容易出现卡死n分钟oom情况.
分成多个partition后,已极大情况避免该情况发生,但是partition设置过高,大量partition同时读取数据库,也可能将数据库弄挂,需要注意.
参考: spark jdbc(mysql) 读取并发度优化