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()
}