如上图,传统的方式如MR、Hive、Spark core方式进行数据ETL操作有如下弊端:
基于这种情况,Spark1.2 诞生了External Data Sources,使用它我们可以非常的方便将外部数据源转换成DF或DS以及将相关数据保存到外External Data Sources。
Spark内置了** Parquet、ORC、JSON、Hive Tables、JDBC To Other DataBases 、Avro**等外部数据源读取和保存操作。详情可参考官网:http://spark.apache.org/docs/latest/sql-data-sources.html,Spark默认的数据源是Parquet,parquet格式数据在读取性能上有良好表现。
如下,标准的load、save数据的流程:
val peopleDF = spark.read.format("json").load("exampleData/people.json")
peopleDF.write.save.format("parquet").save("namesAndFavColors.parquet")
可通过df.cache()方式缓存数据,默认是MEMORY_AND_DISK级别。
一些有用的配置优化:
spark.sql.autoBroadcastJoinThreshold:默认10M,即表数据只有10M时就自动broadcast,这个值太小,具体根据业务进行优化。
spark.sql.shuffle.partitions:默认是200,join操作时ResultStage的parition数量(spark-sql可测试),这个值也是太小了。
主动的广播出去小表,注意注意,一定不要写错了,别把大表广播出去了。
import org.apache.spark.sql.functions.broadcast
broadcast(spark.table("src")).join(spark.table("records"), "key").show()
官网这里提供了很多Spark第三方的jar、工具、项目,比如自定义的数据源。有需要可来这里逛逛,做一名合格的搬运工。
但是在生产上spark内置的以及第三方的外部数据源有时并不能满足我们的需求,这时我们就需要自己Custom Extral DataSource,可参考JDBC数据源的JDBCRelation.scala以及JdbcRelationProvider.scala的实现。
class JdbcRelationProvider extends CreatableRelationProvider
with RelationProvider with DataSourceRegister {。。。。。}
private[sql] case class JDBCRelation(
override val schema: StructType,
parts: Array[Partition],
jdbcOptions: JDBCOptions)(@transient val sparkSession: SparkSession)
extends BaseRelation
with PrunedFilteredScan
with InsertableRelation {。。。。。。。}
如下是列举了一些常见的trait:
之前提到过有两种方式将RDD转成DF,但是这种方式在遇到格式变化时都是比较麻烦的。因此在这里自定义Text外部数据源:
package com.wsk.spark.sql.textdatasource
import org.apache.spark.sql.types.{DataType, LongType, StringType}
/**
* 转换类型
*/
object Utils {
def castTo(value:String, dataType:DataType) ={
dataType match {
case _:LongType => value.toLong
case _:StringType => value
}
}
}
package com.wsk.spark.sql.textdatasource
import org.apache.spark.internal.Logging
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{Row, SQLContext}
import org.apache.spark.sql.sources._
import org.apache.spark.sql.types.{LongType, StringType, StructField, StructType}
/**
* 读取外部数据以及保存
*/
class TextDatasourceRelation(override val sqlContext: SQLContext,
path:String,
userSchema:StructType)
extends BaseRelation with TableScan
// with PrunedScan with PrunedFilteredScan
with Logging{
override def schema: StructType = {
if(userSchema != null){
userSchema
} else {
StructType(
StructField("id",LongType,false) ::
StructField("name",StringType,false) ::
StructField("gender",StringType,false) ::
StructField("salary",LongType,false) ::
StructField("comm",LongType,false) :: Nil
)
}
}
// select * from xxx
override def buildScan(): RDD[Row] = {
logError("this is ruozedata custom buildScan...")
var rdd = sqlContext.sparkContext.wholeTextFiles(path).map(x => {
println(x)
x._2
})
val schemaField = schema.fields
rdd.foreach(println)
// rdd + schemaField
val l = rdd.count()
// rdd + schemaField
val rows = rdd.map(fileContent => {
val lines = fileContent.split("\n")
val data = lines.map(_.split(",").map(x=>x.trim)).toSeq
val result = data.map(x => x.zipWithIndex.map{
case (value, index) => {
val columnName = schemaField(index).name
Utils.castTo(if(columnName.equalsIgnoreCase("gender")) {
if(value == "0") {
"男"
} else if(value == "1"){
"女"
} else {
"未知"
}
} else {
value
}, schemaField(index).dataType)
}
})
result.map(x => Row.fromSeq(x))
})
rows.flatMap(x=>x)
}
// override def buildScan(requiredColumns: Array[String]): RDD[Row] = ???
//
// override def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row] = ???
}
package com.wsk.spark.sql.textdatasource
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.sources.{BaseRelation, RelationProvider, SchemaRelationProvider}
import org.apache.spark.sql.types.StructType
/**
* 注意这类名字必须是DefaultSource,不然会报错
*
*/
class DefaultSource extends RelationProvider with SchemaRelationProvider {
override def createRelation(
sqlContext: SQLContext,
parameters: Map[String, String],
schema: StructType): BaseRelation = {
val path = parameters.get("path")
path match {
case Some(p) => new TextDatasourceRelation(sqlContext, p, schema)
case _ => throw new IllegalArgumentException("path is required...")
}
}
override def createRelation(sqlContext: SQLContext, parameters: Map[String, String]): BaseRelation = {
createRelation(sqlContext, parameters, null)
}
}
package com.wsk.spark.sql.textdatasource
import org.apache.spark.sql.SparkSession
/**
* 测试
*/
object TextApp {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("TextApp").master("local[2]").getOrCreate()
val df = spark.read.format("com.wsk.spark.sql.textdatasource").option("path","D://wskspace/workspace1/data/spark-train/data/textdatasource/*").load()
df.show()
spark.stop()
}
}