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.phoenix</groupId>
	<artifactId>phoenix-spark</artifactId>
	<version>4.14.1-HBase-1.2</version>
	<scope>provided</scope>
</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);
保存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
        )
}
保存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()
}
發佈了14 篇原創文章 · 獲贊 3 · 訪問量 3917
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章