Spark-SQL之自定义数据源的构建

自定义数据源的构建

常见的trait

下面是interfaces.scala中常见的一些接口:

下面各种类、方法,在源码里面都有详细的注释。

//BaseRelation是Spark提供的一个标准的接口
//由于是抽象类,如果要实现自己的外部数据源,必须要实现它里面的一些方法
//这个里面是含有schema的元组集合(字段:字段类型)
//继承了BaseRelation的类,必须以StructType这个形式产生数据的schema
//继承了`Scan`类之后,要实现它里面的相应的方法
@InterfaceStability.Stable
abstract class BaseRelation {
  def sqlContext: SQLContext
  def schema: StructType
.....
}

//在BaseRelation的子类来返回下面几种方式的scan,这个东西由谁创建?由RelationProvider创建

//A BaseRelation that can produce all of its tuples as an RDD of Row objects.
//读取数据,构建RDD[ROW]
//可以理解为select * from xxx   把所有数据读取出来变成RDD[Row]
trait TableScan {
  def buildScan(): RDD[Row]
}

//A BaseRelation that can eliminate unneeded columns before producing an RDD 
//containing all of its tuples as Row objects.
//可以理解为select a,b from xxx   裁剪的scan,读取需要的列变成RDD[Row]
trait PrunedScan {
  def buildScan(requiredColumns: Array[String]): RDD[Row]
}

//可以理解为select a,b from xxx where a>10  读取需要的列,再进行过滤,变成RDD[Row]
trait PrunedFilteredScan {
  def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row]
}

//写数据,插入数据,无返回
trait InsertableRelation {
  def insert(data: DataFrame, overwrite: Boolean): Unit
}

trait CatalystScan {
  def buildScan(requiredColumns: Seq[Attribute], filters: Seq[Expression]): RDD[Row]
}



//用来创建上面的BaseRelation
//传进来指定数据源的参数:比如url、dbtable、user、password等(这个就是你要连接的那个数据源)
//最后返回BaseRelation(已经带有了传进来参数的属性了)
trait RelationProvider {
  def createRelation(sqlContext: SQLContext, parameters: Map[String, String]): BaseRelation
}

// Saves a DataFrame to a destination (using data source-specific parameters)
//mode: SaveMode,当目标已经存在,是用什么方式保存
//parameters: Map[String, String] :指定的数据源参数
//要保存的DataFrame,比如执行查询之后的rows
//返回BaseRelation
trait CreatableRelationProvider {
  def createRelation(
      sqlContext: SQLContext,
      mode: SaveMode,
      parameters: Map[String, String],
      data: DataFrame): BaseRelation
}

//把你的数据源起一个简短的别名
trait DataSourceRegister {
//override def shortName(): String = "parquet"(举例)
  def shortName(): String
}

//比CreatableRelationProvider多了个schema参数
trait SchemaRelationProvider {
  def createRelation(
      sqlContext: SQLContext,
      parameters: Map[String, String],
      schema: StructType): BaseRelation
}

通过JDBCRelation的源码了解外部数据源的执行

点击JdbcRelationProvider ,可以看到它是如何实现的

class JdbcRelationProvider extends CreatableRelationProvider
  with RelationProvider with DataSourceRegister {
  
  override def shortName(): String = "jdbc"

  override def createRelation(
      sqlContext: SQLContext,
      parameters: Map[String, String]): BaseRelation = {

	//这个option就是去连接JDBC的那些信息,比如url、dbtable、user等等
	//具体看JDBCOptions的源码
    val jdbcOptions = new JDBCOptions(parameters)
    val resolver = sqlContext.conf.resolver
    val timeZoneId = sqlContext.conf.sessionLocalTimeZone

	//这个schema如何拿到的???
	//通过JDBC metastore获取得到的
	//具体可以getSchema的源码
    val schema = JDBCRelation.getSchema(resolver, jdbcOptions)
    val parts = JDBCRelation.columnPartition(schema, resolver, timeZoneId, jdbcOptions)
	
	//创建JDBCRelation,JDBCRelation这个是把上面说的那些scan的东西给实现出来
    JDBCRelation(schema, parts, jdbcOptions)(sqlContext.sparkSession)
  }
  override def createRelation(
  ........

下面是JDBCRelation.scala

//可以看一下它里面实现的方法,底层就是拼sql
private[sql] case class JDBCRelation(
    override val schema: StructType,
    parts: Array[Partition],
    jdbcOptions: JDBCOptions)(@transient val sparkSession: SparkSession)  //可以点击scanTable具体分析一下,都是拼SQL
  extends BaseRelation
  with PrunedFilteredScan
  with InsertableRelation {
....................

//实现Scan
  override def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row] = {
    // Rely on a type erasure hack to pass RDD[InternalRow] back as RDD[Row]
    JDBCRDD.scanTable(
      sparkSession.sparkContext,
      schema,
      requiredColumns,
      filters,
      parts,
      jdbcOptions).asInstanceOf[RDD[Row]]
  }
  //实现写数据
    override def insert(data: DataFrame, overwrite: Boolean): Unit = {
    data.write
      .mode(if (overwrite) SaveMode.Overwrite else SaveMode.Append)
      .jdbc(jdbcOptions.url, jdbcOptions.tableOrQuery, jdbcOptions.asProperties)
  }
  ..........
  }

总结:Spark去处理JDBC数据源就是:拼sql,然后交给JDBC API编程,然后产生DataFrame。 上面是JDBC数据源的如何实现的,还有其它数据源比如json、parquet、text等等。

自己实现一个外部数据源(核心重要)

现在有个文本:

//编号、名字、性别、工资、年终奖
101,zhansan,0,10000,200000
102,lisi,0,150000,250000
103,wangwu,1,3000,5
104,zhaoliu,2,500,6

这个文本是没有schema的,之前有两种方式把它转换成DataFrame。一种是通过case class反射的方式,另一种是通过创建带有Rows的RDD,自定义一个schema,然后再用通过createDataFrame来创建DataFrame。

现在通外部数据源把它来实现。

上面的JDBCRelation是通过JdbcRelationProvider来实现的。

定义一个DefaultSource(必须写DefaultSource,源码里定死了,不然会报找不到数据源),继承CreatableRelationProvider,参考上面JDBC的JdbcRelationProvider。 定义一个TextDataSourceRelation,继承BaseRelation和TableScan,并实现TableScan,参考上面JDBC的JdbcRelation。

下面是完整代码:

object TextApp {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .appName("TextApp")
      .master("local[2]")
      .getOrCreate()

    //只要写到包名就可以了...sql.text,不用这样写...sql.text.DefaultSource
    val df = spark.sqlContext.read.format("com.ruozedata.spark.sql.text")
      .load("D:\\data.txt")


    df.show()

    spark.stop()
  }
}
import org.apache.spark.sql.types.{DataType, IntegerType, LongType, StringType}

object Utils {
  def castTo(value:String,dataType:DataType) ={
    dataType match {
      case _ : IntegerType => value.toInt
      case _:LongType =>value.toLong
      case _:StringType => value
    }
  }
}
import org.apache.spark.sql.{DataFrame, SQLContext, SaveMode}
import org.apache.spark.sql.sources.{BaseRelation, CreatableRelationProvider, RelationProvider, SchemaRelationProvider}
import org.apache.spark.sql.types.StructType


//DefaultSource这个名字不能乱写,底层就是自动去拼包名加Datasource,with SchemaRelationProvider  //最佳实践
//RelationProvider用来创建数据的关系,SchemaRelationProvider用来明确schema信息。
class DefaultSource
  extends RelationProvider
    with SchemaRelationProvider {
  override def createRelation(sqlContext: SQLContext, parameters: Map[String, String]): BaseRelation = {
    createRelation(sqlContext,parameters,null)
  }

  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 for custom-datasource format!!")
    }
  }
}
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.{BaseRelation, TableScan}
import org.apache.spark.sql.types.{LongType, StringType, StructField, StructType}

//在编写Relation时,需要实现BaseRelation来重写自定数据源的schema信息,然后实现序列化接口,为了网络传输
class TextDataSourceRelation(override val sqlContext: SQLContext,path:String,userSchema: StructType) extends BaseRelation
  with   Serializable
   with TableScan with Logging {


  //如果传进来的schema不为空,就用传进来的schema,否则就用自定义的schema
  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
      )
    }
  }

  //把数据读进来,读进来之后把它转换成 RDD[Row]
  override def buildScan(): RDD[Row] = {
    logWarning("this is ruozedata buildScan....")
    //读取数据,变成为RDD
    //wholeTextFiles会把文件名读进来,可以通过map(_._2)把文件名去掉,第一位是文件名,第二位是内容
    val rdd = sqlContext.sparkContext.wholeTextFiles(path).map(_._2)

    //拿到schema
    val schemaField = schema.fields

    //rdd.collect().foreach(println)

    //rdd + schemaField 把rdd和schemaField解析出来拼起来
    val rows = rdd.map(fileContent => {
      //拿到每一行的数据
      val lines = fileContent.split("\n")
      //每一行数据按照逗号分隔,分隔之后去空格,然后转成一个seq集合
      val data = lines.map(_.split(",").map(_.trim)).toSeq

      //zipWithIndex
      val result = data.map(x => x.zipWithIndex.map {
        case (value, index) => {

          val columnName = schemaField(index).name
          //castTo里面有两个参数,第一个参数需要给个判断,如果是字段是性别,里面再进行判断再转换一下,如果不是性别就直接用这个字段
          Utils.castTo(if(columnName.equalsIgnoreCase("gender")){
            if(value == "0"){
              "man"
            }else if(value == "1"){
              "woman"
            } else{
              "unknown"
            }
          }else{
            value
          },schemaField(index).dataType)

        }
      })

      result.map(x => Row.fromSeq(x))
    })

    rows.flatMap(x => x)

  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spark SQLSpark中用于处理结构化数据的模块。它提供了一种基于DataFrame和SQL的编程接口,可以方便地进行数据分析和处理。Spark SQL支持多种数据源,包括Hive、JSON、Parquet等,可以通过SQL语句或DataFrame API进行数据查询和操作。Spark SQL还支持用户自定义函数(UDF)和聚合函数(UDAF),可以满足更复杂的数据处理需求。Spark SQL的优势在于它可以与Spark的其他模块无缝集成,如Spark Streaming、MLlib等,可以构建完整的数据处理和分析流程。 ### 回答2: 本篇笔记主要是介绍Spark SQL的基本概念和编程模型。 Spark SQL是面向Spark计算引擎的一种高性能的分布式数据处理技术,它提供一种基本的高度抽象的编程模型,使得开发大规模的数据仓库和数据分析应用变得容易和高效。 Spark SQL最核心的概念就是DataFrames,DataFrame是RDD的超集,提供了更高层次的抽象和对数据的结构化的处理能力,在数据处理的过程中常常会用到一些基本的操作:过滤、选择、聚合、排序等等,而这些操作都可以一步一步地以DataFrame为基础完成。 在使用Spark SQL的过程中,可以通过DataFrame API和Spark SQL语言两种方式进行编程。DataFrame API是Spark SQL提供的一种编程API,它提供了常见的操作,如选择、过滤和聚合等。而Spark SQL语言则是一种基于SQL的编程语言,和传统的SQL查询语言类似,可以通过SQL查询语句来对数据进行查询和操作。Spark SQL可以支持多种数据源,包括JSON、Parquet、ORC、Hive、JDBC等等,因此可以轻松地读取和处理不同类型的数据源Spark SQL还提供了高级的功能,如User-Defined Functions(UDFs)、Window Functions和Structured Streaming等等。UDFs允许开发者自定义函数并在Spark SQL中使用,将SQL和代码结合起来,提高了处理数据的灵活性和可扩展性;Window Functions则是一种用来进行滑动窗口操作的函数,常常用于计算数据的局部或全局统计量;Structured Streaming提供了数据流处理的能力,并且实现了端到端的Exactly-Once语义。 总之,Spark SQL提供了很多的功能和便利,特别是在大数据处理和分析领域,它的优势尤为突出。结合Spark的强大计算能力和Spark SQL的抽象编程模型,在大规模的数据分析和仓库方面都具有非常高的可扩展性和灵活性。 ### 回答3: Spark SQLSpark生态系统中的一个组件,它负责处理结构化数据。它提供了SQL查询和DataFrame API,可以从不同的数据源中读取和处理数据。Spark SQL能够理解SQL语言,这使得开发人员可以使用传统的SQL查询方式来处理数据,同时还可以利用Spark的优势,例如分布式计算和内存缓存。 Spark SQL支持许多不同类型的数据源,包括Hive表、传统的RDD、Parquet文件、JSON文件、CSV文件和JDBC数据源等。Spark SQL可以通过使用数据源API将这些数据源加载到Spark中,然后可以在Spark中处理和查询这些数据。 Spark SQL还支持特定于数据源的优化器和执行引擎,这允许Spark SQL针对不同的数据源执行优化操作。例如,使用Hive数据源时,Spark SQL会使用Hive的元数据来优化查询计划。当使用Parquet文件格式时,Spark SQL会使用Parquet文件中的元数据来优化查询计划。 在Spark SQL中,DataFrame是一种非常重要的概念。它是一种强类型的分布式数据集,可以使用DataFrame API进行操作。DataFrame API是一种更面向数据的API,例如过滤数据、聚合数据等。Spark SQL中的DataFrame可以看作是类似于表的对象,它可以和Spark SQL中的SQL查询混合使用。 除了DataFrame API和SQL查询,Spark SQL还支持UDF(用户自定义函数)。UDF允许用户在SQL查询或DataFrame API中定义自己的函数,以实现更复杂的数据操作。使用UDF时,用户可以使用累加器和广播变量等Spark的分布式计算功能,使得UDF具备高性能和可伸缩性。 总之,Spark SQL是大数据处理领域中一种非常方便和强大的处理结构化数据的工具。它可以方便地与其他Spark组件结合使用,例如Spark Streaming、Spark MLlib等。使用Spark SQL,开发人员可以在不同的数据源之间轻松地查询和转换数据,并利用Spark分布式计算的优势,实现高性能和可伸缩性的数据处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值