HBase&Spark集成 -- DataFrame

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 数据所需的步骤。

https://databricks.com/wp-content/uploads/2020/09/DataSourcesApiDiagram-min.png

注意事项

​ 使用 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 catalogsave函数将创建一个包含 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"*/
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小中.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值