列 减枝 + 谓词下推

自定义sparkSQL数据源的过程中,需要对sparkSQL表的schema和Hbase表的schema进行整合;

对于spark来说,要想自定义数据源,你可以实现这3个接口:

BaseRelation 代表了一个抽象的数据源。该数据源由一行行有着已知schema的数据组成(关系表)。
TableScan 用于扫描整张表,将数据返回成RDD[Row]。
RelationProvider 顾名思义,根据用户提供的参数返回一个数据源(BaseRelation)。

第一步, 定义 HBASE /SPARKSQL| 对应 SCHEMA 的 基本 接口 定义。

package object hbase {

//类型的封装(spark和hbase)

abstract class SchemaField extends Serializable

//spark的schema封装

case class RegisterSchemaField(fieldName:String , fieldType:String) extends SchemaField with Serializable

//hbase的schema封装

case class HbaseSchemaField(fieldName:String , fieldType:String) extends SchemaField with Serializable

为了解析 HBASE对应的行列内容,定义一个统一的OBJECT来进行 管理。

```java
object Resolver extends Serializable {

  //解析rowkey
  private def resolveRowkey[T<:Result , S<:String](result:Result , resultType:String):Any = {
    val  rowkey = resultType match{
      case "String" => result.getRow.map(_.toChar).mkString.toString
      case "Int" => result.getRow.map(_.toChar).mkString.toInt
      case "Long" => result.getRow.map(_.toChar).mkString.toLong
      case "Double" => result.getRow.map(_.toChar).mkString.toDouble
      case "Float" => result.getRow.map(_.toChar).mkString.toFloat
    }
    rowkey
  }

匹配 对应 列 和列簇

```java
private def resolveColumn[T<:Result , S<:String , A<:String , B<:String](result: Result ,
                                                                         columnFamily:String,columnName:String ,
                                                                           resultType:String):Any


val column = result.containsColumn(columnFamily.getBytes , columnName.getBytes) match{
 case true =>
 resultType match {
 case “String” =>Bytes.toString(result.getValue(Bytes.toBytes(columnFamily) , Bytes.toBytes(columnName)))
 case “Int” =>Bytes.toInt(result.getValue(Bytes.toBytes(columnFamily) , Bytes.toBytes(columnName)))
 case “Double” =>Bytes.toDouble(result.getValue(Bytes.toBytes(columnFamily) , Bytes.toBytes(columnName)))
 case “Long” =>Bytes.toLong(result.getValue(Bytes.toBytes(columnFamily) , Bytes.toBytes(columnName)))
 case “Float” =>Bytes.toFloat(result.getValue(Bytes.toBytes(columnFamily) , Bytes.toBytes(columnName)))

再统一提供对外 的 服务 接口:

```java
def resolve(hbaseSchemaField: HbaseSchemaField , result: Result):Any = {
    //cf:field1 cf:field2
    val split: Array[String] = hbaseSchemaField.fieldName.split(":" , -1)
    val cfName = split(0)
    val colName = split(1)

    //把rowkey或者列 返回
    var resolveResullt:Any = null
    if(cfName == "" && colName=="key"){
      resolveResullt = resolveRowkey(result , hbaseSchemaField.fieldType)
    }else{
      resolveResullt = resolveColumn(result , cfName , colName , hbaseSchemaField.fieldType)
    }
    resolveResullt

  }
在 SPARK 内核源码中,有如下 LOAD定义。

```java
def load(paths: String*): DataFrame = {
    sparkSession.baseRelationToDataFrame(
      DataSource.apply(
        sparkSession,
        paths = paths,
        userSpecifiedSchema = userSpecifiedSchema,
        className = source,
        options = extraOptions.toMap).resolveRelation())
  }

def resolveRelation(checkFilesExist: Boolean = true): BaseRelation = {
    val relation = (providingClass.newInstance(), userSpecifiedSchema) match
     
     case (dataSource: SchemaRelationProvider, Some(schema)) =>
        dataSource.createRelation(sparkSession.sqlContext, caseInsensitiveOptions, schema)
      case (dataSource: RelationProvider, None) =>
        dataSource.createRelation(sparkSession.sqlContext, caseInsensitiveOptions)

对应 providingClass 定义:

lazy val providingClass: Class[_] = DataSource.lookupDataSource(className)

def lookupDataSource(provider: String): Class[_] = {
    val provider1 = backwardCompatibilityMap.getOrElse(provider, provider)
    val provider2 = s"$provider1.DefaultSource"

注意这里DefaultSouce , 自定义 的数据源,一定要 定义这样名称 的CLASS。
对应继承的 基类 也应 对应 如下定义。

case (dataSource: RelationProvider, None) =>
        dataSource.createRelation(sparkSession.sqlContext, caseInsensitiveOptions)

对应定义 的 类 名称 。

class DefaultSource extends RelationProvider with CreatableRelationProvider

为了 方便 后续 HBASE对 DATASOURCE的Relation 操作, 另外定义一个 接口 来操作,接口为 HBASERelation .

private[hbase] case class HbaseRelation(@transient val hbaseProps:Map[String , String])
                   (@transient val sqlContext:SQLContext) extends BaseRelation with TableScan /* 这里 生产 上 要采用 剪支或者 谓词下推 的SCAN,比如 PrunedScan 和 PrunedFilteredScan*/

后续就是对 HBASE RELATION 的操作了

  • 1、你要查那张表
  • 2、你查询表的rowkey范围是什么
  • 3、schema:spark的schema和hbase的schema是是什么样**

第一步, 定义 查询的表名。
//1 你要查那张表
val hbaseTableName = hbaseProps.getOrElse(“hbase_table_name” , sys.error(“无法拿到查询的表名称”))
第二步, 定义 查询 ROW KEY 范围。

第三步, 或许 HBASE 要查询的列 的定义。

val hbaseTableSchema = hbaseProps.getOrElse(“hbase_table_schema” , sys.error(" 获取不到查询的hbase列"))

第四步, 获取 SPARK 要查询列定义
val registerTableSchema = hbaseProps.getOrElse(“sparksql_table_schema” , sys.error(" 获取不到查询的hbase列"))

第五步,构造 SCHEMA FIELD 针对 HBASE的结构。

```java
private def extractHbaseSchema[T<:String](hbaseTableSchema:String):Array[HbaseSchemaField] = {
    //5.1:去掉括号 MM:id , MM:create_time
    val fieldStr: String = hbaseTableSchema.trim.drop(1).dropRight(1)
    //MM:id , MM:create_time ---> [MM:id] , [MM:create_time]
    val fieldArray: Array[String] = fieldStr.split(",").map(_.trim)
    val hbaseSchemaFields = fieldArray.map { field =>
      HbaseSchemaField(field, "")
    }
    hbaseSchemaFields
  }

第六步,构造 SPARK 的 SHCEMA FIELD

private def extractRegisterSchemaT<:String:Array[RegisterSchemaField] = {
 //driver_id String , create_time String
 val fieldStr: String = registerTableSchema.trim.drop(1).dropRight(1)
 val fieldArr: Array[String] = fieldStr.split(",").map(_.trim)
 fieldArr.map{field =>
 val strings: Array[String] = field.split("\s+", -1)
 RegisterSchemaField(strings(0) , strings(1))
 }
 }
第七,第八步, 调用 上面定义函数,拿到 HBASE 和SPARK 的 SCHEMA 的原始数据 的拆分。

//7:
  private val tmpHbaseSchemaField: Array[HbaseSchemaField] = extractHbaseSchema(hbaseTableSchema)
  //8
  private val registerTableFields: Array[RegisterSchemaField] = extractRegisterSchema(registerTableSchema)

第九步, 就是把 HBASE ,SPARK 的SHCEMA 进行 合并,或者叫拉链。

```java
private def tableSchemaFieldMapping[T<:Array[HbaseSchemaField] , S<:Array[RegisterSchemaField]](tmpHbaseSchemaField: Array[HbaseSchemaField] , registerTableFields: Array[RegisterSchemaField]):mutable.LinkedHashMap[HbaseSchemaField , RegisterSchemaField] = {
    if(tmpHbaseSchemaField.length != registerTableFields.length){
      sys.error("两个scchema不一致")
    }
    //把两个表拉链起来
    val zip: Array[(HbaseSchemaField, RegisterSchemaField)] = tmpHbaseSchemaField.zip(registerTableFields)
    //封装到有序的map里面
    val map = new mutable.LinkedHashMap[HbaseSchemaField , RegisterSchemaField]
    for(arr <- zip){
      map.put(arr._1 , arr._2)
    }
    map
  }

第十步,定义出一个有序 的 MAP 定义,调用 第九步的函数。

private val zipSchema: mutable.LinkedHashMap[HbaseSchemaField, RegisterSchemaField] = tableSchemaFieldMapping(tmpHbaseSchemaField , registerTableFields)

第十一步, 把HBASE 里面 的 数据类型给补充进去。

private def feedType[T<:mutable.LinkedHashMap[HbaseSchemaField, RegisterSchemaField]](zipSchema: mutable.LinkedHashMap[HbaseSchemaField, RegisterSchemaField]):Array[HbaseSchemaField] = {
    val finalHbaseSchema: mutable.Iterable[HbaseSchemaField] = zipSchema.map {
      case (hbaseSchema, registerSchema) =>
        hbaseSchema.copy(fieldType = registerSchema.fieldType)
    }
    finalHbaseSchema.toArray

  }

第12步, 把HBASE 对应数据,加入类型定义,并且定义出一个

private val finalHbaseSchema: Array[HbaseSchemaField] = feedType(zipSchema)

第13,14步, 要构建 HBASE 查询时需要 的列簇 信息,

private def getHbaseSearchSchema[T<:Array[HbaseSchemaField]](finalHbaseSchema: Array[HbaseSchemaField]):String = {
 var str = ArrayBufferString finalHbaseSchema.foreach{field =>
 if(!isRowkey(field)){
 str.append(field.fieldName)
 }
 }
 str.mkString(" “)
 }
 //14:识别rowkey MM:col1
 private def isRowkey[T<:HbaseSchemaField](hbaseSchemaField: HbaseSchemaField):Boolean = {
 val split: Array[String] = hbaseSchemaField.fieldName.split(”:" , -1)
 val cf = split(0)
 val col = split(1)
 if(cf == null && col ==“key”) true else false
 }

第15,16步, 将 上面包含了类型定义的HBASE 和SPARK 再次进行拉链,形成一个类型和数据完全对应的LINKHASHMAP,以及HBASE 的查询范围 的 规定。

//15
  private val searchColumns: String = getHbaseSearchSchema(finalHbaseSchema)

  //16  再次拉链 , 把有类型的habse和spark做拉链====schema
  private val fieldStructFileds: mutable.LinkedHashMap[HbaseSchemaField, RegisterSchemaField] = tableSchemaFieldMapping(finalHbaseSchema , registerTableFields)

拉链 表 组成 之后,开始进行 BaseRelation 类 里面定义的,重载 schema :structType 类型。

override def schema: StructType = {
    /*先用 finalHbaseSchema ,里面包含添加了数据类型的HBASE 数据内容*/
    val fields: Array[StructField] = finalHbaseSchema.map { field =>
      /*再用 fieldStructFileds 的HASHMAP结构,获取SparkSchema下的 内容*/
      val name: RegisterSchemaField = fieldStructFileds.getOrElse(field, sys.error(s"这个${field}拿不到!!"))
      val relationType = field.fieldType match {
        case "String" => SchemaType(StringType, nullable = false)
        case "Int" => SchemaType(IntegerType, nullable = false)
        case "Long" => SchemaType(LongType, nullable = false)
        case "Double" => SchemaType(DoubleType, nullable = false)
      }

      StructField(name.fieldName, relationType.dataType, relationType.nullable)
    }
    StructType(fields)
  }

再定义一个 HADOOP 查询之后 对应的 RDD结构,可以进行 RDD 和 DATAFRAME 转换,方便SPARKSQL 的查询操作。重载 TABLESCAN 里面 定义的BUILDSCAN() 方法。

override def buildScan(): RDD[Row] = {
    val hbaseConf = HBaseConfiguration.create()
    hbaseConf.set("hbase.zookeeper.quorum", GlobalConfigUtils.getProp("hbase.zookeeper.quorum"))//指定zookeeper的地址
    hbaseConf.set(TableInputFormat.INPUT_TABLE, hbaseTableName)//指定要查询表名
    hbaseConf.set(TableInputFormat.SCAN_COLUMNS, searchColumns)//指定要查询的列
    hbaseConf.set(TableInputFormat.SCAN_ROW_START, startRowkey)//指定查询的rowkey范围
    hbaseConf.set(TableInputFormat.SCAN_ROW_STOP, endRowkey)
    hbaseConf.set(TableInputFormat.SCAN_CACHEDROWS , "10000")//指定查询时候,缓存多少数据
    hbaseConf.set(TableInputFormat.SHUFFLE_MAPS , "1000")

    //通过newAPIHadoopRDD查询
    val hbaseRdd: RDD[(ImmutableBytesWritable, Result)] = sqlContext.sparkContext.newAPIHadoopRDD(
      hbaseConf,
      classOf[TableInputFormat],
      classOf[ImmutableBytesWritable],
      classOf[Result]
    )
    //Result -->RDD[Row] --->DataFrame

    val finalResult: RDD[Row] = hbaseRdd.map(line => line._2).map { result =>
      // 构建一个buffer , 用来接收hbase的结果
      val values = new ArrayBuffer[Any]()
      finalHbaseSchema.foreach { field =>
        values += Resolver.resolve(field, result)
      }
      Row.fromSeq(values)
    }
    finalResult
  }
}