下面这一段是废话,时间紧的兄弟直接跳过:
前几天接触了 SparkSQL,通过自定义数据源可以完成各种数据库的读取和写入。我好像嗅到了数据中台的调调,封装一个扩展性强的小架架把 hbase,mysql,redis各种数据源都整合一下,再用并发多线程,对象池之类的优化一下性能,再招一个3000块的小表哥,多么优秀的开源节流,是不是又可以找老板涨工资了!考验架构能力的时候到了,不想当架构师的程序员不是一个好男人!好了,做梦时间结束,进入正题:
我们知道 hbase 最终是把数据转成了 HFile 文件,HFile 是 hadoop 的二进制格式文件,所以从 hbase 读出来的也是二进制字节流,那么要如何获取每个 Column 的数据类型呢?
其实这是一个伪命题,因为 SparkSQL 已经帮我们实现了!
在自定义数据源需要实现的第二层接口(DataSourceReader)需要实现这样一个方法:
override def readSchema(): StructType = {
structType
}
StructType 就是保存字段类型信息的,进入 DataSourceReader 接口看下方法调用:
private lazy val readerFactories: java.util.List[DataReaderFactory[UnsafeRow]] = reader match {
case r: SupportsScanUnsafeRow => r.createUnsafeRowReaderFactories()
case _ =>
reader.createDataReaderFactories().asScala.map {
new RowToUnsafeRowDataReaderFactory(
_,
reader.readSchema() // here
): DataReaderFactory[UnsafeRow]
}.asJava
}
进入 RowToUnsafeRowDataReaderFactory
class RowToUnsafeRowDataReaderFactory(rowReaderFactory: DataReaderFactory[Row], schema: StructType)
extends DataReaderFactory[UnsafeRow] {
override def preferredLocations: Array[String] = rowReaderFactory.preferredLocations
override def createDataReader: DataReader[UnsafeRow] = {
new RowToUnsafeDataReader(
rowReaderFactory.createDataReader, // 这里的 Reader 就是我们自己实现的 DataReader
RowEncoder.apply(schema).resolveAndBind() // 这是 column 的类型信息
)
}
}
继续进入 RowToUnsafeDataReader
class RowToUnsafeDataReader(val rowReader: DataReader[Row], encoder: ExpressionEncoder[Row])
extends DataReader[UnsafeRow] {
override def next: Boolean = rowReader.next
/**
* rowReader 就是我们自己实现的 DataReader 对象
* 这里他用了一个对象代理了我们自己实现的 DataReader 对象
* get() 的时候 用具有 column 类型信息的 encoder 对我们返回的 Row 对象做了一次转换
*/
override def get: UnsafeRow = encoder.toRow(rowReader.get).asInstanceOf[UnsafeRow]
override def close(): Unit = rowReader.close()
}
而我们自定义数据源重写 DataReader 的时候,只需要获取一个数组对象array,然后直接调用 Row.fromSeq(array) 转成 Row 对象返回即可
override def get(): Row = {
// 将查询的数据直接生成 Iterator 对象,get()直接iterator.next
val result: Result = datas.next()
// 拆分 hbase 的列族和列名,获取结果,将结果封装成一个 String 数组
val strings: Array[String] = cfcc.split(",").map(eachCfcc => {
val strings: Array[String] = eachCfcc.trim.split(":")
// 直接转 String
Bytes.toString(result.getValue(strings(0).trim.getBytes(), strings(1).trim.getBytes()))
})
Row.fromSeq(strings)
}