HBase&Spark集成 – DataFrame
Apache HBase 是一个成熟的 NoSQL 存储系统,已在大规模生产部署中得到证明。尽管 HBase 是一个键值存储,但对于像 SQL 一样更轻松地访问数据的需求很高。Apache Spark SQL 提供了基本过滤和插入数据的强大支持。hbase-connectors子项目提供了HBase 与SparkSQL 的集成。
hbase -spark 集成 利用 Spark-1.2.0 版本中引入的DataSource API。HBase-Spark 连接器在简单的 HBase KV存储和复杂的SQL 查询之间架起了一座桥梁,使用户能够使用 Spark 在 HBase 之上执行复杂的数据分析。HBase DataFrame 是标准的 Spark DataFrame,能够与任何其他数据源交互,例如 Hive、ORC、Parquet、JSON 等。hbase-spark 集成 应用了分区修剪、列修剪、谓词下推和数据局部性等关键技术。本文描述了以Spark SQL 读写 HBase 数据所需的步骤。
注意事项
使用 hbase-spark 集成 连接器,用户需要为 HBase 和 Spark 表之间定义字段(columns)映射 Catalog,准备数据并填充 HBase 表,然后加载 HBase DataFrame。用户可以通过 SQL 查询进行集成查询和访问 HBase 表中的记录。
官网参考文档:https://hbase.apache.org/book.html#_sparksqldataframes
按照官方文档给的案例,集成过程中遇到一些异常,具体解决方案请参考完整流程。
异常1:
Exception in thread "main" java.lang.NullPointerException
at org.apache.hadoop.hbase.spark.HBaseRelation.<init>(DefaultSource.scala:138)
at org.apache.hadoop.hbase.spark.DefaultSource.createRelation(DefaultSource.scala:69)
at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:318)
at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:223)
at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211)
at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:167)
at com.huoli.test.PortraitTest2$.withCatalog$1(PortraitTest2.scala:105)
at com.huoli.test.PortraitTest2$.main(PortraitTest2.scala:107)
at com.huoli.test.PortraitTest2.main(PortraitTest2.scala)
原因:HBaseSparkConf配置类中的默认配置如下:
val USE_HBASECONTEXT = "hbase.spark.use.hbasecontext"
val DEFAULT_USE_HBASECONTEXT = true
默认为true,如用户不指定hbase.spark.use.hbasecontext为false,程序执行不会创建HBaseContext而是去获取最后一次创建的HBaseContext(程序重来没有创建过HBaseContext,默认值为null),但程序后续去获取hbaseContext的配置就出现了NullPointerException异常。
解决方案:
1、按表配置,覆盖HBaseSparkConf 中设置的值。如果不设置,默认值将生效。
Spark读写HBase时加上option(“hbase.spark.use.hbasecontext”, false)或option(“hbase.spark.use.hbasecontext”, false)问题就解决啦
**注意:**程序第一次读写hbase加上就可以,后续不用加。避免重复创建hbaseContext对象。
spark
.read
.option(HBaseSparkConf.USE_HBASECONTEXT, false)
.options(Map(HBaseTableCatalog.tableCatalog->cat))
.format("org.apache.hadoop.hbase.spark")
.load()
2、读写数据之前创建一个 HBaseContext 推荐使用
val config = HBaseConfiguration.create()
new HBaseContext(spark.sparkContext, config)
异常2:
Exception in thread "main" org.apache.hadoop.hbase.TableNotFoundException: table1
at org.apache.hadoop.hbase.client.ConnectionImplementation.locateRegionInMeta(ConnectionImplementation.java:842)
at org.apache.hadoop.hbase.client.ConnectionImplementation.locateRegion(ConnectionImplementation.java:737)
at org.apache.hadoop.hbase.client.ConnectionImplementation.locateRegion(ConnectionImplementation.java:723)
at org.apache.hadoop.hbase.client.ConnectionImplementation.locateRegion(ConnectionImplementation.java:694)
at org.apache.hadoop.hbase.client.ConnectionImplementation.getRegionLocation(ConnectionImplementation.java:572)
原因:没有找到table1这张表,改为namespace:table1
1、导入依赖(根据spark版本导入)
<!-- spark3.0以下 -->
<!-- https://mvnrepository.com/artifact/org.apache.hbase.connectors.spark/hbase-spark -->
<dependency>
<groupId>org.apache.hbase.connectors.spark</groupId>
<artifactId>hbase-spark</artifactId>
<version>1.0.0</version>
</dependency>
<!-- spark3.0及以上 -->
<!-- https://mvnrepository.com/artifact/org.apache.hbase.connectors.spark/hbase-spark -->
<dependency>
<groupId>org.apache.hbase.connectors.spark</groupId>
<artifactId>hbase-spark3</artifactId>
<version>1.0.0.7.2.12.2-5</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
hbase-spark连接器源码:https://github.com/apache/hbase-connectors/tree/master/spark
2、导入HBase配置文件
将HBase的配置文件hbase-site.xml导入项目resources目录下。
3、定义catalog
def cat = s"""{
|"table":{"namespace":"portrait", "name":"portrait:table1"},
|"rowkey":"key",
|"columns":{
|"col0":{"cf":"rowkey", "col":"key", "type":"string"},
|"col1":{"cf":"cf1", "col":"col1", "type":"boolean"},
|"col2":{"cf":"cf1", "col":"col2", "type":"double"},
|"col3":{"cf":"cf1", "col":"col3", "type":"float"},
|"col4":{"cf":"cf1", "col":"col4", "type":"int"},
|"col5":{"cf":"cf1", "col":"col5", "type":"bigint"},
|"col6":{"cf":"cf1", "col":"col6", "type":"smallint"},
|"col7":{"cf":"cf1", "col":"col7", "type":"string"},
|"col8":{"cf":"cf1", "col":"col8", "type":"tinyint"}
|}
|}""".stripMargin
Catalog 定义了 HBase 和 Spark 表之间的映射。该目录有两个关键部分。一个是rowkey定义,另一个是Spark中表列与HBase中列族和列限定符的映射。上面定义了一个 HBase 表的模式,名称为 table1,行键为键,列数(col1 -
col8)。请注意,rowkey 还必须详细定义为具有特定 cf (rowkey) 的列 (col0)。
4、保存数据框
case class HBaseRecord(
col0: String,
col1: Boolean,
col2: Double,
col3: Float,
col4: Int,
col5: Long,
col6: Short,
col7: String,
col8: Byte)
object HBaseRecord {
def apply(i: Int): HBaseRecord = {
val s = s"""row${"%03d".format(i)}"""
HBaseRecord(s,
i % 2 == 0,
i.toDouble,
i.toFloat,
i,
i.toLong,
i.toShort,
s"String$i extra",
i.toByte)
}
}
5、Spark保存数据到HBase
val data = (0 to 255).map { i =>
HBaseRecord(i)
}
spark.sparkContext.parallelize(data).toDF.write
.option("hbase.spark.use.hbasecontext", false)
.options(
Map(HBaseTableCatalog.tableCatalog -> cat, HBaseTableCatalog.newTable -> "5"))
.format("org.apache.hadoop.hbase.spark")
.save()
data
用户准备的是具有 256 个 HBaseRecord 对象的本地 Scala 集合。 sc.parallelize(data)
函数分布data
形成一个RDD。toDF
返回一个DataFrame。 write
函数返回一个 DataFrameWriter,用于将 DataFrame 写入外部存储系统(例如此处的 HBase)。给定一个具有指定模式的 DataFrame catalog
,save
函数将创建一个包含 5 个region的 HBase 表并将 DataFrame 保存在其中。
6、Spark加载HBase数据
def withCatalog(cat: String): DataFrame = {
spark
.read
.option("hbase.spark.use.hbasecontext", false)
.options(Map(HBaseTableCatalog.tableCatalog->cat))
.format("org.apache.hadoop.hbase.spark")
.load()
}
在 ‘withCatalog’ 函数中,sqlContext 是 SQLContext 的一个变量,是在 Spark 中处理结构化数据(行和列)的入口点。 read
返回一个 DataFrameReader,可用于将数据作为 DataFrame 读取。 option
函数将底层数据源的输入选项添加到 DataFrameReader,format
函数指定 DataFrameReader 的输入数据源格式。该load()
函数将输入作为 DataFrame 加载。withCatalog函数返回的DataFrame(df)可以用来访问HBase表。
7、完整代码
import org.apache.hadoop.hbase.spark.datasources.HBaseTableCatalog
import org.apache.spark.sql.{DataFrame, SparkSession}
case class HBaseRecord(
col0: String,
col1: Boolean,
col2: Double,
col3: Float,
col4: Int,
col5: Long,
col6: Short,
col7: String,
col8: Byte)
object HBaseRecord {
def apply(i: Int): HBaseRecord = {
val s = s"""row${"%03d".format(i)}"""
HBaseRecord(s,
i % 2 == 0,
i.toDouble,
i.toFloat,
i,
i.toLong,
i.toShort,
s"String$i extra",
i.toByte)
}
}
object HBaseSource {
def cat = s"""{
|"table":{"namespace":"portrait", "name":"portrait:table1"},
|"rowkey":"key",
|"columns":{
|"col0":{"cf":"rowkey", "col":"key", "type":"string"},
|"col1":{"cf":"cf1", "col":"col1", "type":"boolean"},
|"col2":{"cf":"cf1", "col":"col2", "type":"double"},
|"col3":{"cf":"cf1", "col":"col3", "type":"float"},
|"col4":{"cf":"cf1", "col":"col4", "type":"int"},
|"col5":{"cf":"cf1", "col":"col5", "type":"bigint"},
|"col6":{"cf":"cf1", "col":"col6", "type":"smallint"},
|"col7":{"cf":"cf1", "col":"col7", "type":"string"},
|"col8":{"cf":"cf1", "col":"col8", "type":"tinyint"}
|}
|}""".stripMargin
def main(args: Array[String]) {
val spark = SparkSession.builder()
.master("local[*]")
.appName("HBaseSourceExample")
.getOrCreate()
import spark.implicits._
def withCatalog(cat: String): DataFrame = {
spark
.read
.option("hbase.spark.use.hbasecontext", false)
.options(Map(HBaseTableCatalog.tableCatalog->cat))
.format("org.apache.hadoop.hbase.spark")
.load()
}
val data = (0 to 255).map { i =>
HBaseRecord(i)
}
spark.sparkContext.parallelize(data).toDF.write
.option("hbase.spark.use.hbasecontext", false)
.options(
Map(HBaseTableCatalog.tableCatalog -> cat))
.format("org.apache.hadoop.hbase.spark")
.save()
val df = withCatalog(cat)
df.show()
}
}
https://github.com/hortonworks-spark/shc
HBase DataFrame filter异常 @TODO
HBase DataFrame 使用filter表达式、过滤条件会报org.apache.hadoop.hbase.DoNotRetryIOException异常, filter使用函数却不会出现异常。
// 报异常
df.filter("col4 == 50").show()
df.filter($"col4" === 50).show
//没有异常
df.filter(u=> u.getAs[Int]("col4") == 50).show()
异常信息:
org.apache.hadoop.hbase.DoNotRetryIOException: org.apache.hadoop.hbase.DoNotRetryIOException: java.lang.reflect.InvocationTargetException
at org.apache.hadoop.hbase.protobuf.ProtobufUtil.toFilter(ProtobufUtil.java:1478)
at org.apache.hadoop.hbase.protobuf.ProtobufUtil.toScan(ProtobufUtil.java:993)
at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2396)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:33648)
at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2180)
at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:112)
at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:133)
at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:108)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.hadoop.hbase.protobuf.ProtobufUtil.toFilter(ProtobufUtil.java:1474)
... 8 more
Caused by: java.lang.NoClassDefFoundError: org/apache/hadoop/hbase/spark/datasources/JavaBytesEncoder$
at org.apache.hadoop.hbase.spark.datasources.JavaBytesEncoder.create(JavaBytesEncoder.scala)
at org.apache.hadoop.hbase.spark.SparkSQLPushDownFilter.parseFrom(SparkSQLPushDownFilter.java:200)
... 13 more
解决方案
Spark读取HBases数据时会进行谓词下推,表达式、过滤条件不能转换为HBase的过滤器 猜测 尚未证实
option("hbase.spark.pushdown.columnfilter", false)
读取时间戳区间的数据
options(Map(HBaseTableCatalog.tableCatalog->cat,HBaseSparkConf.TIMERANGE_START -> "1649252574525",
HBaseSparkConf.TIMERANGE_END -> "1649252574560")) HBaseSparkConf.TIMESTAMP -> "1649252574525"*/