列 减枝 + 谓词下推
自定义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
}
}