Spark系列 —— Spark整合读写HBase、Phoenix

HBase作为一种可以进行海量数据存储、并进行高性能读写的NoSQL数据库,在大数据中有着广泛的引用,而Spark作为常用的大数据计算引擎,需要访问存储HBase中的海量数据进行分析处理。那么Spark如何整合HBase来加载HBase中的表,以及将外部数据持久化到HBase,这就是接下来要介绍的两种方式:直接读写HBase和通过Phoenix读写HBase。
 

一、Spark整合HBase

1. Spark加载HBase表

加载HBase表并指定过滤条件,转化成RDD,再将RDD转化成Dataset,便可以做一些SQL关联处理。

代码如下:

import org.apache.hadoop.hbase.client., Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.types.{IntegerType, StringType, StructField, StructType}


def readHBase(spark: SparkSession): Unit = {
    val config = HBaseConfiguration.create()
    config.set(TableInputFormat.INPUT_TABLE, "test") //表名
    config.set(TableInputFormat.SCAN_ROW_START, "start_key") //扫描起始rowKey
    config.set(TableInputFormat.SCAN_ROW_STOP, "stop_key") //扫描终止rowKey

    //HBase表加载为RDD[(K, V)]
    val rdd = spark.sparkContext.newAPIHadoopRDD(config,
        classOf[TableInputFormat],
        classOf[ImmutableBytesWritable],
        classOf[Result]
    )

    //从Result中获取指定列最新版本的值
    //rdd转为RDD[Row]
    val rdd1 = rdd.map(m => {
        //获取一行查询结果
        val result: Result = m._2

        val rowKey = Bytes.toString(result.getRow) //获取row key
        val userId = Bytes.toString(result.getValue("cf".getBytes,"user_id".getBytes))
        val name = Bytes.toString(result.getValue("cf".getBytes,"name".getBytes))
        val age = Bytes.toString(result.getValue("cf".getBytes,"age".getBytes))

        Row(rowKey, userId, name, age)
    })

    //创建schema
    val schema = StructType(
      StructField("user_id", IntegerType, false) ::
      StructField("name", StringType, false) ::
      StructField("age", IntegerType, true) :: Nil)

    //RDD转为DataFrame
    val df = spark.createDataFrame(rdd1, schema)
    
    df.select("name", "age")
}

 

2. Spark持久化数据到HBase

在一些将批量数据导入HBase系统的场景中,如果调用Put API单条导入HBase,很可能会给RegionServer带来较大的写入负载,比如,会出现消耗大量资源(CPU、带宽、IO)、引起RegionServer频繁flush、引起RegionServer频繁GC等问题。而Bulk Load首先是使用MapReduce将待写入数据转换成HFile文件,再直接将这些HFile文件加载到集群中。BulkLoad并没有将写入请求发送给RegionServer,所以并不会出现上述一系列的问题。

代码如下:

import com.alibaba.fastjson.JSON
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.{HFileOutputFormat2, TableInputFormat}
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.hbase.{HBaseConfiguration, KeyValue}
import org.apache.hadoop.mapreduce.Job
import org.apache.spark.sql.{Dataset, SparkSession}

import scala.collection.mutable.ArrayBuffer


def writeHBase(spark: SparkSession, datasetJson: Dataset[String]): Unit = {
    //假设datasetJson是一个json字符串类型的Dataset
    //结构为"{"name":"", "age":"", "phone":"", "address":"", }"
    val ds = spark.read.json(datasetJson)
    val rdd = ds.rdd.mapPartitions(iterator => {
        iterator.map(m => {
            //json字符串解析成JSONObject
            val json = JSON.parseObject(m.toString())
            //phone作为KeyValue的row key
            val phone = json.getString("phone")
            //以便遍历其他所有键值对
            json.remove("phone")

            //键值对字节序列
            val writable = new ImmutableBytesWritable(Bytes.toBytes(phone))
            //初始化数组, 存储JSONObject中的键值对
            val array = ArrayBuffer[(ImmutableBytesWritable, KeyValue)]()

            //JSON中的key作为Hbase表中的列名,并按字典序排序
            val jsonKeys = json.keySet().toArray
                .map(_.toString).sortBy(x => x)

            val length = jsonKeys.length
            for (i <- 0 until length) {
                val key = jsonKeys(i)
                val value = json.get(jsonKeys(i)).toString
                //KeyValue为HBase中的基本类型Key/Value。
                //构造函数中的参数依次为:rowkey、列族、列名、值。
                //Json对象中的每个key和其值构成HBase中每条记录的value
                val keyValue: KeyValue = new KeyValue(
                    Bytes.toBytes(phone),  //row key
                    "cf".getBytes(), //列族名
                    key.getBytes(), //列名
                    value.getBytes()) //列的值

                array += ((writable, keyValue))
            }
            array
        })
        //重新分区,减少保存的文件数
        //展开数组中的元素
        //对rowkey排序
    }).repartition(1).flatMap(x => x).sortByKey()

    val config = HBaseConfiguration.create()
    config.set(TableInputFormat.INPUT_TABLE, "test") //表名

    val job = Job.getInstance(config)
    job.setMapOutputKeyClass(classOf[ImmutableBytesWritable])
    job.setMapOutputValueClass(classOf[KeyValue])

    //持久化到HBase表
    rdd.saveAsNewAPIHadoopFile("/tmp/test",
        classOf[ImmutableBytesWritable],
        classOf[KeyValue],
        classOf[HFileOutputFormat2],
        job.getConfiguration)

}

 

二、Spark整合Phoenix

要使用phoenix-spark插件,需要在pom.xml(Maven工程)文件中添加如下依赖:

<dependency>
	<groupId>org.apache.phoenixgroupId>
	<artifactId>phoenix-sparkartifactId>
	<version>4.14.1-HBase-1.2version>
	<scope>providedscope>
dependency>

我这里使用的Spark版本为2.3.1,HBase版本为1.2.0。
 

1. Spark加载Phoenix表

import org.apache.hadoop.conf.Configuration
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import org.apache.phoenix.spark._


//方法一:使用数据源API加载Phoenix表为一个DataFrame
def readPhoenix(spark: SparkSession): Unit = {
    val df = spark.read
        .format("org.apache.phoenix.spark")
        .options(Map("table" -> "TEST", "zkUrl" -> "host:2181"))
        .load()

    df.show()
}


//方法二:使用Configuration对象加载Phoenix表为一个DataFrame
def readPhoenix(spark: SparkSession): Unit = {
    
    val conf = new Configuration()
    conf.set("hbase.zookeeper.quorum", "hostname:2181")
    val df = spark.sqlContext.phoenixTableAsDataFrame(
        "test", //表名
        Seq("NAME", "AGE"), //指定要加载的列名
        predicate = Some("PHONE = 13012340000"), //可设置where条件
        conf = conf)

    df.show()
}


//方法三:使用Zookeeper URL加载Phoenix表为一个RDD
def readPhoenix(spark: SparkSession): Unit = {
    val rdd =spark.sparkContext.phoenixTableAsRDD(
        "TEST",
        Seq("NAME", "AGE"),
        predicate = Some("PHONE = 13012340000"),
        zkUrl = Some("hostname:2181") //Zookeeper URL来连接Phoenix
    )

    rdd.map(_.get(""))
}

 

2. Spark持久化数据到Phoenix

创建Phoenix表DDL:

CREATE TABLE TEST(id BIGINT NOT NULL PRIMARY KEY, col1 VARCHAR, col2 INTEGER);
2.1 保存RDD中的数据到Phoenix表

代码如下:

import org.apache.spark.sql.SparkSession
import org.apache.phoenix.spark._


def writePhoenix(spark: SparkSession): Unit = {
    val dataSet = List((1L, "1", 1), (2L, "2", 2), (3L, "3", 3))
    spark.sparkContext.parallelize(dataSet)
        .saveToPhoenix(
            "TEST", //表名
            Seq("ID","COL1","COL2"), //列命名
            zkUrl = Some("host:2181") //Zookeeper URL
        )
}
2.2 保存DataFrame中的数据到Phoenix表

代码如下:

def writePhoenix(spark: SparkSession): Unit = {
    val df = spark.read
        .format("org.apache.phoenix.spark")
        .options(Map("table" -> "TEST", "zkUrl" -> "host:2181"))
        .load()
	
	//方法一
    df.saveToPhoenix(Map("table" -> "TEST", "zkUrl" -> "host:2181"))

	//方法二
    df.write
        .format("org.apache.phoenix.spark")
        .mode("overwrite")
        .option("table", "OUTPUT_TABLE")
        .option("zkUrl", "host:2181")
        .save()
}

你可能感兴趣的:(Spark)