Spark(16) -- 資料讀取與儲存的主要方式

erainm發表於2020-10-05

在這裡插入圖片描述

1. 文字檔案輸入輸出

 當我們將一個文字檔案讀取為 RDD 時,輸入的每一行都會成為RDD的一個元素。也可以將多個完整的文字檔案一次性讀取為一個pair RDD, 其中鍵是檔名,值是檔案內容
 val input = sc.textFile("./README.md")
 如果傳遞目錄,則將目錄下的所有檔案讀取作為RDD。
 檔案路徑支援萬用字元
 通過wholeTextFiles()對於大量的小檔案讀取效率比較高,大檔案效果沒有那麼高。
 Spark通過saveAsTextFile() 進行文字檔案的輸出,該方法接收一個路徑,並將 RDD 中的內容都輸入到路徑對應的檔案中。Spark 將傳入的路徑作為目錄對待,會在那個目錄下輸出多個檔案。這樣,Spark 就可以從多個節點上並行輸出了
 result.saveAsTextFile(outputFile)

scala> sc.textFile("./README.md")
res6: org.apache.spark.rdd.RDD[String] = ./README.md MapPartitionsRDD[7] at textFile at <console>:25

scala> val readme = sc.textFile("./README.md")
readme: org.apache.spark.rdd.RDD[String] = ./README.md MapPartitionsRDD[9] at textFile at <console>:24

scala> readme.collect()
res7: Array[String] = Array(# Apache Spark, "", Spark is a fast and general cluster...
scala> readme.saveAsTextFile("hdfs://node01:8020/test")

2. JSON檔案輸入輸出

 如果JSON檔案中每一行就是一個JSON記錄,那麼可以通過將JSON檔案當做文字檔案來讀取,然後利用相關的JSON庫對每一條資料進行JSON解析。

scala> import org.json4s._  
import org.json4s._

scala> import org.json4s.jackson.JsonMethods._  
import org.json4s.jackson.JsonMethods._

scala> import org.json4s.jackson.Serialization  
import org.json4s.jackson.Serialization

scala> var result = sc.textFile("examples/src/main/resources/people.json")
result: org.apache.spark.rdd.RDD[String] = examples/src/main/resources/people.json MapPartitionsRDD[7] at textFile at <console>:47

scala> implicit val formats = Serialization.formats(ShortTypeHints(List())) 
formats: org.json4s.Formats{val dateFormat: org.json4s.DateFormat; val typeHints: org.json4s.TypeHints} = org.json4s.Serialization$$anon$1@61f2c1da

scala>  result.collect()
res3: Array[String] = Array({"name":"Michael"}, {"name":"Andy", "age":30}, {"name":"Justin", "age":19})

 如果JSON資料是跨行的,那麼只能讀入整個檔案,然後對每個檔案進行解析。
 JSON資料的輸出主要是通過在輸出之前將由結構化資料組成的 RDD 轉為字串 RDD,然後使用 Spark 的文字檔案 API 寫出去
 說白了還是以文字檔案的形式存,只是文字的格式已經在程式中轉換為JSON。

3. CSV檔案輸入輸出

 讀取 CSV/TSV 資料和讀取 JSON 資料相似,都需要先把檔案當作普通文字檔案來讀取資料,然後通過將每一行進行解析實現對CSV的讀取
 CSV/TSV資料的輸出也是需要將結構化RDD通過相關的庫轉換成字串RDD,然後使用 Spark 的文字檔案 API 寫出去。

4. SequenceFile檔案輸入輸出

 SequenceFile檔案是Hadoop用來儲存二進位制形式的key-value對而設計的一種平面檔案(Flat File)
  Spark 有專門用來讀取 SequenceFile 的介面。在 SparkContext 中,可以呼叫 sequenceFile[keyClass, valueClass](path)
在這裡插入圖片描述
在這裡插入圖片描述

scala> val data=sc.parallelize(List((2,"aa"),(3,"bb"),(4,"cc"),(5,"dd"),(6,"ee")))
data: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[16] at parallelize at <console>:24

scala>  data.saveAsSequenceFile("hdfs://node01:8020/sequdata")
scala> val sdata = sc.sequenceFile[Int,String]("hdfs://node01:8020/sequdata/*")
sdata: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[19] at sequenceFile at <console>:24

scala> sdata.collect()
res14: Array[(Int, String)] = Array((2,aa), (3,bb), (4,cc), (5,dd), (6,ee))

可以直接呼叫 saveAsSequenceFile(path) 儲存你的PairRDD,它會幫你寫出資料。需要鍵和值能夠自動轉為Writable型別。

5. 物件檔案輸入輸出

 物件檔案是將物件序列化後儲存的檔案,採用Java的序列化機制。可以通過objectFile[k,v](path) 函式接收一個路徑,讀取物件檔案,返回對應的 RDD,也可以通過呼叫saveAsObjectFile() 實現對物件檔案的輸出。因為是序列化所以要指定型別。

scala> val data=sc.parallelize(List((2,"aa"),(3,"bb"),(4,"cc"),(5,"dd"),(6,"ee")))
data: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[20] at parallelize at <console>:24

scala> data.saveAsObjectFile("hdfs://node01:8020/objfile")
scala> import org.apache.spark.rdd.RDD
import org.apache.spark.rdd.RDD

scala> val objrdd =sc.objectFile[(Int,String)]("hdfs://node01:8020/objfile/p*")
objrdd: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[28] at objectFile at <console>:25

scala> objrdd.collect()
res20: Array[(Int, String)] = Array((2,aa), (3,bb), (4,cc), (5,dd), (6,ee))

6. Hadoop輸入輸出格式

 Spark的整個生態系統與Hadoop是完全相容的,所以對於Hadoop所支援的檔案型別或者資料庫型別,Spark也同樣支援.另外,由於Hadoop的API有新舊兩個版本,所以Spark為了能夠相容Hadoop所有的版本,也提供了兩套建立操作介面.對於外部儲存建立操作而言,hadoopRDD和newHadoopRDD是最為抽象的兩個函式介面,主要包含以下四個引數.

  1. 輸入格式(InputFormat): 制定資料輸入的型別,如TextInputFormat等,新舊兩個版本所引用的版本分別是org.apache.hadoop.mapred.InputFormat和org.apache.hadoop.mapreduce.InputFormat(NewInputFormat)
  2. 鍵型別: 指定[K,V]鍵值對中K的型別
  3. 值型別: 指定[K,V]鍵值對中V的型別
  4. 分割槽值: 指定由外部儲存生成的RDD的partition數量的最小值,如果沒有指定,系統會使用預設值defaultMinSplits。
     其他建立操作的API介面都是為了方便最終的Spark程式開發者而設定的,是這兩個介面的高效實現版本.例如,對於textFile而言,只有path這個指定檔案路徑的引數,其他引數在系統內部指定了預設值

   相容舊版本HadoopAPI的建立操作

檔案路徑輸入格式鍵型別值型別分割槽值
textFile(path: String, minPartitions: Int = defaultMinPartitions)pathTextInputFormatLongWritableTextminSplits
hadoopFile[K, V, F <: InputFormat[K, V]](path: String, minPartitions: Int)(implicit km: ClassTag[K], vm: ClassTag[V], fm: ClassTag[F]): RDD[(K, V)]pathFKVminSplits
hadoopFile[K, V, F <: [K, V]](path: String)(implicit km: ClassTag[K], vm: ClassTag[V], fm: ClassTag[F]): RDD[(K, V)]pathFKVDefaultMinSplits
hadoopFile[K, V](path: String, inputFormatClass: Class[_ <: InputFormat[K, V]], keyClass: Class[K], valueClass: Class[V], minPartitions: Int = defaultMinPartitions): RDD[(K, V)]pathinputFormatClasskeyClassvalueClassdefaultMinPartitions
hadoopRDD[K, V](conf: JobConf, inputFormatClass: Class[_ <: InputFormat[K, V]], keyClass: Class[K], valueClass: Class[V], minPartitions: Int = defaultMinPartitions): RDD[(K, V)]n/ainputFormatClasskeyClassvalueClassdefaultMinPartitions
sequenceFile[K, V](path: String, minPartitions: Int = defaultMinPartitions)(implicit km: ClassTag[K], vm: ClassTag[V], kcf: () ⇒ WritableConverter[K], vcf: () ⇒ WritableConverter[V]): RDD[(K, V)]pathSequenceFileInputFormat[K,V]KVdefaultMinPartitions
objectFile[T](path: String, minPartitions: Int = defaultMinPartitions)(implicit arg0: ClassTag[T]): RDD[T]pathSequenceFileInputFormat[NullWritable,BytesWritable]NullWritableBytesWritableminSplits

相容新版本HadoopAPI的建立操作

檔案路徑輸入格式鍵型別值型別分割槽值
newAPIHadoopFile[K, V, F <: InputFormat[K, V]](path: String, fClass: Class[F], kClass: Class[K], vClass: Class[V], conf: Configuration = hadoopConfiguration): RDD[(K, V)]pathFKVn/a
newAPIHadoopFile[K, V, F <: InputFormat[K, V]](path: String)(implicit km: ClassTag[K], vm: ClassTag[V], fm: ClassTag[F]): RDD[(K, V)]pathFKVn/a
newAPIHadoopRDD[K, V, F <: InputFormat[K, V]](conf: Configuration = hadoopConfiguration, fClass: Class[F], kClass: Class[K], vClass: Class[V]): RDD[(K, V)]n/aFKVn/a

注意:

  1. 在Hadoop中以壓縮形式儲存的資料,不需要指定解壓方式就能夠進行讀取,因為Hadoop本身有一個解壓器會根據壓縮檔案的字尾推斷解壓演算法進行解壓.
  2. 如果用Spark從Hadoop中讀取某種型別的資料不知道怎麼讀取的時候,上網查詢一個使用map-reduce的時候是怎麼讀取這種這種資料的,然後再將對應的讀取方式改寫成上面的hadoopRDD和newAPIHadoopRDD兩個類就行了.

讀取示例:


scala> import org.apache.hadoop.io._
import org.apache.hadoop.io._
scala> val data = sc.parallelize(Array((30,"hadoop"), (71,"hive"), (11,"cat")))
data: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[47] at parallelize at <console>:35

scala> data.saveAsNewAPIHadoopFile("hdfs://node01:8020/output4/",classOf[LongWritable] ,classOf[Text] ,classOf[org.apache.hadoop.mapreduce.lib.output.TextOutputFormat[LongWritable, Text]])

 對於RDD最後的歸宿除了返回為集合標量,也可以將RDD儲存到外部檔案系統或者資料庫中,Spark系統與Hadoop是完全相容的,所以MapReduce所支援的讀寫檔案或者資料庫型別,Spark也同樣支援.另外,由於Hadoop的API有新舊兩個版本,所以Spark為了能夠相容Hadoop所有的版本,也提供了兩套API.
 將RDD儲存到HDFS中在通常情況下需要關注或者設定五個引數,即檔案儲存的路徑,key值的class型別,Value值的class型別,RDD的輸出格式(OutputFormat,如TextOutputFormat/SequenceFileOutputFormat),以及最後一個相關的引數codec(這個參數列示壓縮儲存的壓縮形式,如DefaultCodec,Gzip,Codec等等)

  相容舊版API

saveAsObjectFile(path: String): Unit
saveAsTextFile(path: String, codec: Class[_ <: CompressionCodec]): Unit
saveAsTextFile(path: String): Unit
saveAsHadoopFile[F <: OutputFormat[K, V]](path: String)(implicit fm: ClassTag[F]): Unit
saveAsHadoopFile[F <: OutputFormat[K, V]](path: String, codec: Class[_ <: CompressionCodec])(implicit fm: ClassTag[F]): Unit
saveAsHadoopFile(path: String, keyClass: Class[], valueClass: Class[], outputFormatClass: Class[_ <: OutputFormat[_, ]], codec: Class[ <: CompressionCodec]): Unit
saveAsHadoopDataset(conf: JobConf): Unit

 這裡列出的API,前面6個都是saveAsHadoopDataset的簡易實現版本,僅僅支援將RDD儲存到HDFS中,而saveAsHadoopDataset的引數型別是JobConf,所以其不僅能夠將RDD儲存到HDFS中,也可以將RDD儲存到其他資料庫中,如Hbase,MangoDB,Cassandra等.

  相容新版API

saveAsNewAPIHadoopFile(path: String, keyClass: Class[], valueClass: Class[], outputFormatClass: Class[_ <: OutputFormat[_, _]], conf: Configuration = self.context.hadoopConfiguration): Unit
saveAsNewAPIHadoopFile[F <: OutputFormat[K, V]](path: String)(implicit fm: ClassTag[F]): Unit
saveAsNewAPIHadoopDataset(conf: Configuration): Unit

 同樣的,前2個API是saveAsNewAPIHadoopDataset的簡易實現,只能將RDD存到HDFS中,而saveAsNewAPIHadoopDataset比較靈活.新版的API沒有codec的引數,所以要壓縮儲存檔案到HDFS中每需要使用hadoopConfiguration引數,設定對應mapreduce.map.output.compress.codec引數和mapreduce.map.output.compress引數.
注意:
1.如果不知道怎麼將RDD儲存到Hadoop生態的系統中,主要上網搜尋一下對應的map-reduce是怎麼將資料儲存進去的,然後改寫成對應的saveAsHadoopDataset或saveAsNewAPIHadoopDataset就可以了.

寫入示例:

scala> val read =  sc.newAPIHadoopFile[LongWritable, Text, org.apache.hadoop.mapreduce.lib.input.TextInputFormat]("hdfs://node01:8020/output4/part*", classOf[org.apache.hadoop.mapreduce.lib.input.TextInputFormat], classOf[LongWritable], classOf[Text])
read: org.apache.spark.rdd.RDD[(org.apache.hadoop.io.LongWritable, org.apache.hadoop.io.Text)] = hdfs://node01:8020/output4/part* NewHadoopRDD[48] at newAPIHadoopFile at <console>:35

scala> read.map{case (k, v) => v.toString}.collect
res44: Array[String] = Array(30 hadoop, 71      hive, 11        cat)

7. 檔案系統的輸入輸出

 Spark 支援讀寫很多種檔案系統, 像本地檔案系統、Amazon S3、HDFS等。

8. 資料庫的輸入輸出

8.1 關係型資料庫連線

 實際開發中常常將分析結果RDD儲存至MySQL表中,使用foreachPartition函式;此外Spark中提供JdbcRDD用於從MySQL表中讀取資料。

 呼叫RDD#foreachPartition函式將每個分割槽資料儲存至MySQL表中,儲存時考慮降低RDD分割槽數目和批量插入,提升程式效能。

建資料庫和表語句

CREATE DATABASE bigdata CHARACTER SET utf8; 
 CREATE TABLE `t_student` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
SELECT * FROM t_student

 支援通過Java JDBC訪問關係型資料庫。需要通過JdbcRDD進行,示例如下:

Mysql讀取

def main (args: Array[String] ) {
  val sparkConf = new SparkConf ().setMaster ("local[2]").setAppName ("JdbcApp")
  val sc = new SparkContext (sparkConf)

  val rdd = new org.apache.spark.rdd.JdbcRDD (
    sc,
    () => {
      Class.forName ("com.mysql.jdbc.Driver").newInstance()
      java.sql.DriverManager.getConnection ("jdbc:mysql://localhost:3306/rdd", "root", "hive")
    },
    "select * from rddtable where id >= ? and id <= ?;",
    1,
    10,
    1,
    r => (r.getInt(1), r.getString(2)))

  println (rdd.count () )
  rdd.foreach (println (_) )
  sc.stop ()
}

Mysql寫入:

def main(args: Array[String]) {
  val sparkConf = new SparkConf().setMaster("local[2]").setAppName("HBaseApp")
  val sc = new SparkContext(sparkConf)
  val data = sc.parallelize(List("Female", "Male","Female"))

  data.foreachPartition(insertData)
}

def insertData(iterator: Iterator[String]): Unit = {

	Class.forName ("com.mysql.jdbc.Driver").newInstance()
	//將資料存入到MySQL
    //獲取連線
  val conn = java.sql.DriverManager.getConnection("jdbc:mysql://localhost:3306/rdd", "root", "admin")
  iterator.foreach(data => {
  	//將每一條資料存入到MySQL
  	val ps = conn.prepareStatement("insert into rddtable(name) values (?)")
    ps.setString(1, data) 
    ps.executeUpdate()
  })
}

JdbcRDD 接收這樣幾個引數。

  • 首先,要提供一個用於對資料庫建立連線的函式。這個函式讓每個節點在連線必要的配置後建立自己讀取資料的連線
  • 接下來,要提供一個可以讀取一定範圍內資料的查詢,以及查詢引數中lowerBound和 upperBound 的值。這些引數可以讓 Spark 在不同機器上查詢不同範圍的資料,這樣就不會因嘗試在一個節點上讀取所有資料而遭遇效能瓶頸。
  • 這個函式的最後一個引數是一個可以將輸出結果從轉為對運算元據有用的格式的函式。如果這個引數空缺,Spark會自動將每行結果轉為一個物件陣列。

Cassandra資料庫和ElasticSearch整合:

在這裡插入圖片描述

8.2 HBase資料庫

 由於 org.apache.hadoop.hbase.mapreduce.TableInputFormat 類的實現,Spark 可以通過Hadoop輸入格式訪問HBase。這個輸入格式會返回鍵值對資料,其中鍵的型別為org. apache.hadoop.hbase.io.ImmutableBytesWritable,而值的型別為org.apache.hadoop.hbase.client.Result。

 Spark可以從HBase表中讀寫(Read/Write)資料,底層採用TableInputFormat和TableOutputFormat方式,與MapReduce與HBase整合完全一樣,使用輸入格式InputFormat和輸出格式OutputFoamt。

在這裡插入圖片描述

8.2.1 HBase Sink

 回顧MapReduce向HBase表中寫入資料,使用TableReducer,其中OutputFormat為TableOutputFormat,讀取資料Key:ImmutableBytesWritable,Value:Put。

 寫入資料時,需要將RDD轉換為RDD[(ImmutableBytesWritable, Put)]型別,呼叫saveAsNewAPIHadoopFile方法資料儲存至HBase表中。

 HBase Client連線時,需要設定依賴Zookeeper地址相關資訊及表的名稱,通過Configuration設定屬性值進行傳遞。
在這裡插入圖片描述

範例演示:將詞頻統計結果儲存HBase表,表的設計
在這裡插入圖片描述

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Put
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableOutputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 將RDD資料儲存至HBase表中
 */
object SparkWriteHBase {
    
    def main(args: Array[String]): Unit = {
        // 建立應用程式入口SparkContext例項物件
        val sc: SparkContext = {
            // 1.a 建立SparkConf物件,設定應用的配置資訊
            val sparkConf: SparkConf = new SparkConf()
                .setAppName(this.getClass.getSimpleName.stripSuffix("$"))
                .setMaster("local[2]")
            // 1.b 傳遞SparkConf物件,構建Context例項
            new SparkContext(sparkConf)
        }
        sc.setLogLevel("WARN")
        
        // TODO: 1、構建RDD
        val list = List(("hadoop", 234), ("spark", 3454), ("hive", 343434), ("ml", 8765))
        val outputRDD: RDD[(String, Int)] = sc.parallelize(list, numSlices = 2)
        
        // TODO: 2、將資料寫入到HBase表中, 使用saveAsNewAPIHadoopFile函式,要求RDD是(key, Value)
        // TODO: 組裝RDD[(ImmutableBytesWritable, Put)]
        /**
         * HBase表的設計:
         * 表的名稱:htb_wordcount
         *      Rowkey:  word
         *      列簇:    info
         *      欄位名稱: count
         */
        val putsRDD: RDD[(ImmutableBytesWritable, Put)] = outputRDD.mapPartitions{ iter =>
            iter.map { case (word, count) =>
                // 建立Put例項物件
                val put = new Put(Bytes.toBytes(word))
                // 新增列
                put.addColumn(
                    // 實際專案中使用HBase時,插入資料,先將所有欄位的值轉為String,再使用Bytes轉換為位元組陣列
                    Bytes.toBytes("info"), Bytes.toBytes("cout"), Bytes.toBytes(count.toString)
                )
                // 返回二元組
                (new ImmutableBytesWritable(put.getRow), put)
            }
        }
        
        // 構建HBase Client配置資訊
        val conf: Configuration = HBaseConfiguration.create()
        // 設定連線Zookeeper屬性
        conf.set("hbase.zookeeper.quorum", "node1.itcast.cn")
        conf.set("hbase.zookeeper.property.clientPort", "2181")
        conf.set("zookeeper.znode.parent", "/hbase")
        // 設定將資料儲存的HBase表的名稱
        conf.set(TableOutputFormat.OUTPUT_TABLE, "htb_wordcount")
        /*
             def saveAsNewAPIHadoopFile(
                 path: String,// 儲存的路徑
                 keyClass: Class[_], // Key型別
                 valueClass: Class[_], // Value型別
                 outputFormatClass: Class[_ <: NewOutputFormat[_, _]], // 輸出格式OutputFormat實現
                 conf: Configuration = self.context.hadoopConfiguration // 配置資訊
             ): Unit
         */
        putsRDD.saveAsNewAPIHadoopFile(
            "datas/spark/htb-output-" + System.nanoTime(), //
            classOf[ImmutableBytesWritable], //
            classOf[Put], //
            classOf[TableOutputFormat[ImmutableBytesWritable]], //
            conf
        )
        
        // 應用程式執行結束,關閉資源
        sc.stop()
    }
}

執行完成以後,使用hbase shell檢視資料:
在這裡插入圖片描述

8.2.2 HBase Source

 回顧MapReduce從讀HBase表中的資料,使用TableMapper,其中InputFormat為TableInputFormat,讀取資料Key:ImmutableBytesWritable,Value:Result。
 從HBase表讀取資料時,同樣需要設定依賴Zookeeper地址資訊和表的名稱,使用Configuration設定屬性,形式如下:
在這裡插入圖片描述
 此外,讀取的資料封裝到RDD中,Key和Value型別分別為:ImmutableBytesWritable和Result,不支援Java Serializable導致處理資料時報序列化異常。設定Spark Application使用Kryo序列化,效能要比Java 序列化要好,建立SparkConf物件設定相關屬性,如下所示:
在這裡插入圖片描述
範例演示:

從HBase表讀取詞頻統計結果,程式碼如下

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{CellUtil, HBaseConfiguration}
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.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 從HBase 表中讀取資料,封裝到RDD資料集
 */
object SparkReadHBase {
    
    def main(args: Array[String]): Unit = {
        // 建立應用程式入口SparkContext例項物件
        val sc: SparkContext = {
            // 1.a 建立SparkConf物件,設定應用的配置資訊
            val sparkConf: SparkConf = new SparkConf()
                .setAppName(this.getClass.getSimpleName.stripSuffix("$"))
                .setMaster("local[2]")
                // TODO: 設定使用Kryo 序列化方式
                .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
                // TODO: 註冊序列化的資料型別
                .registerKryoClasses(Array(classOf[ImmutableBytesWritable], classOf[Result]))
            // 1.b 傳遞SparkConf物件,構建Context例項
            new SparkContext(sparkConf)
        }
        sc.setLogLevel("WARN")
        
        // TODO: a. 讀取HBase Client 配置資訊
        val conf: Configuration = HBaseConfiguration.create()
        conf.set("hbase.zookeeper.quorum", "node1.itcast.cn")
        conf.set("hbase.zookeeper.property.clientPort", "2181")
        conf.set("zookeeper.znode.parent", "/hbase")
        
        // TODO: b. 設定讀取的表的名稱
        conf.set(TableInputFormat.INPUT_TABLE, "htb_wordcount")
        /*
             def newAPIHadoopRDD[K, V, F <: NewInputFormat[K, V]](
                 conf: Configuration = hadoopConfiguration,
                 fClass: Class[F],
                 kClass: Class[K],
                 vClass: Class[V]
             ): RDD[(K, V)]
         */
        val resultRDD: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(
            conf, //
            classOf[TableInputFormat], //
            classOf[ImmutableBytesWritable], //
            classOf[Result] //
        )
        
        println(s"Count = ${resultRDD.count()}")
        resultRDD
            .take(5)
            .foreach { case (rowKey, result) =>
                println(s"RowKey = ${Bytes.toString(rowKey.get())}")
                // HBase表中的每條資料封裝在result物件中,解析獲取每列的值
                result.rawCells().foreach { cell =>
                    val cf = Bytes.toString(CellUtil.cloneFamily(cell))
                    val column = Bytes.toString(CellUtil.cloneQualifier(cell))
                    val value = Bytes.toString(CellUtil.cloneValue(cell))
                    val version = cell.getTimestamp
                    println(s"\t $cf:$column = $value, version = $version")
                }
            }
        
        // 應用程式執行結束,關閉資源
        sc.stop()
    }   
}

執行結果:
在這裡插入圖片描述

HBase簡單讀取:

def main(args: Array[String]) {
  val sparkConf = new SparkConf().setMaster("local[2]").setAppName("HBaseApp")
  val sc = new SparkContext(sparkConf)

  val conf = HBaseConfiguration.create()
  //HBase中的表名
  conf.set(TableInputFormat.INPUT_TABLE, "fruit")

  val hBaseRDD = sc.newAPIHadoopRDD(conf, classOf[TableInputFormat],
    classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable],
    classOf[org.apache.hadoop.hbase.client.Result])

  val count = hBaseRDD.count()
  println("hBaseRDD RDD Count:"+ count)
  hBaseRDD.cache()
  hBaseRDD.foreach {
    case (_, result) =>
      val key = Bytes.toString(result.getRow)
      val name = Bytes.toString(result.getValue("info".getBytes, "name".getBytes))
      val color = Bytes.toString(result.getValue("info".getBytes, "color".getBytes))
      println("Row key:" + key + " Name:" + name + " Color:" + color)
  }
  sc.stop()
}

HBase簡單寫入:

def main(args: Array[String]) {
  val sparkConf = new SparkConf().setMaster("local[2]").setAppName("HBaseApp")
  val sc = new SparkContext(sparkConf)

  val conf = HBaseConfiguration.create()
  val jobConf = new JobConf(conf)
  jobConf.setOutputFormat(classOf[TableOutputFormat])
  jobConf.set(TableOutputFormat.OUTPUT_TABLE, "fruit_spark")

  val fruitTable = TableName.valueOf("fruit_spark")
  val tableDescr = new HTableDescriptor(fruitTable)
  tableDescr.addFamily(new HColumnDescriptor("info".getBytes))

  val admin = new HBaseAdmin(conf)
  if (admin.tableExists(fruitTable)) {
    admin.disableTable(fruitTable)
    admin.deleteTable(fruitTable)
  }
  admin.createTable(tableDescr)

  def convert(triple: (Int, String, Int)) = {
    val put = new Put(Bytes.toBytes(triple._1))
    put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(triple._2))
    put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("price"), Bytes.toBytes(triple._3))
    (new ImmutableBytesWritable, put)
  }
  val initialRDD = sc.parallelize(List((1,"apple",11), (2,"banana",12), (3,"pear",13)))
  val localData = initialRDD.map(convert)

  localData.saveAsHadoopDataset(jobConf)
}

相關文章