SparkSQL讀取和寫出數據的幾種方式

一、parquet格式的數據

parquet是一種列式存儲格式的文件類型。存儲時可以通過牛X的壓縮算法節省存儲空間,讀取數據時只需要讀取所需的列,提高讀取性能

1. 生成parquet格式的數據

使用 Idea 或 ScalaIDE 創建一個 maven 項目,pom依賴在文章的末尾;在項目中創建一個 scala class,編寫如下代碼:

PS:不清楚如何創建Spark項目的小夥伴可以看我的這篇文章(如何開發SparkSQL項目?),其實創建Spark的項目和maven的項目沒啥太大的區別,只是添加了一個ScalaSDK,在 IDEA 上安裝了一個插件而已,很簡單。

import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.{SaveMode, SparkSession}

import scala.collection.mutable.ArrayBuffer

/**
 * 用戶樣本類
 * @param user_id 用戶id
 * @param user_name 用戶名稱
 */
case class User(user_id: Int, user_name: String)

object ParquetDemo {
  def main(args: Array[String]): Unit = {
  	// 配置SparkSession的相關信息
    val conf = new SparkConf().setAppName("parquetDemo").setMaster("local[*]")
    // 創建SparkSession
    val spark = SparkSession.builder().config(conf).getOrCreate()
    // 獲取SparkContext
    val sc = spark.sparkContext
    // 注:在實際spark開發中上面的幾行代碼基本屬於標配,固定格式,不會有太大的改動

    // 創建一個可變數組,向數組中添加10個用戶
    val rows = ArrayBuffer[User]()
    for (i <- 0 until 10) {
      rows += User(i, s"name${i}")
    }
    // 將數組轉變成不可變數組
    val rowArray = rows.toArray
    // 使用用戶數組來創建RDD
    val rowRDD = sc.parallelize(rowArray)

    // 導入spark的隱式轉換函數toDF,將RDD轉換成DataFrame
    import spark.implicits._
    // 會在當前項目的根目錄創建一個spark-warehouse文件夾,裏面會有一個user文件夾,裏面會生成一些.parquet文件
    rowRDD.toDF().write.mode(SaveMode.Overwrite).saveAsTable("user")
  }
}

運行上述代碼,我們會在項目根目錄下發現生成了如下.parquet文件:
在這裏插入圖片描述

  • _SUCCESS文件是一個標記文件,裏面沒有內容,是spark程序運行成功的一個標示(MapReduce運行成功時也有)
  • 文件後綴爲.parquet的文件就是真正的數據文件了。
  • 其餘的以.crc文件結尾的是一些校驗文件,只有在windows平臺下運行纔會生成,在Linux平臺上運行是不會生成的,無需理會。

2. 讀取parquet格式的數據

我們通過前一個例子生成了.parquet格式的文件,打開一看發現都是亂碼,那麼我們該如何讀取查看這類.parquet格式的數據呢?

在前一個示例代碼的基礎上,添加如下代碼即可讀取.parquet格式的數據:

// 把下面生成.parquet文件這行代碼註釋掉
// rowRDD.toDF().write.mode(SaveMode.Overwrite).saveAsTable("user")

// 讀取.parquet格式的文件
val localDF = spark.read.load("E:\\personal-project\\localhive\\spark-warehouse\\user")
// 展示讀取到的數據
localDF.show

再次運行代碼,運行結果如下:
在這裏插入圖片描述
成功讀取到了我們之前模擬生成的用戶數據,而且顯示的格式還非常好看。

二、JSON格式的數據

1. 讀取json格式的數據

json 格式的數據大家都清楚長什麼樣,所以這個小例子先介紹如何讀取 json 格式的數據,首先準備如下 json 格式的數據:

注意:大家在將下面的內容複製到文件中之後,把換行符都刪掉,sparkSQL在讀取json數據時會將換行符也解析成一列,帶有換行符會使解析出來的結果不符合預期。

[
	{"user_id":"1","user_name":"jack"},
	{"user_id":"2","user_name":"tom"},
	{"user_id":"3","user_name":"mick"},
	{"user_id":"4","user_name":"liuneng"},
	{"user_id":"5","user_name":"wangwu"},
	{"user_id":"6","user_name":"zhaosi"},
	{"user_id":"7","user_name":"lisan"}
]

spark代碼如下:

import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession

object JsonDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("JsonDemo").setMaster("local[*]")
    val spark = SparkSession.builder().config(conf).getOrCreate()
    val sc = spark.sparkContext

	// 讀取json文件中的數據到DataFrame
    val jsonDF = spark.read.json("E:/people.json")
    // 打印到控制檯上
    jsonDF.show()
  }
}

運行程序,程序執行結果如下:
在這裏插入圖片描述

2. 生成json格式的數據

在上面代碼的基礎上,添加如下兩行代碼即可:

// 會在項目的根目錄下創建一個名爲jsonFile的目錄,目錄裏面有生成的json格式的數據
jsonDF.write.json("jsonFile")

運行程序,會發現如下結果:
在這裏插入圖片描述
在這裏插入圖片描述
在項目的根目錄下生成了一個名爲 jsonFile 的目錄,目錄中有生成的json格式的文件,文件內容如下:
在這裏插入圖片描述

三、通過JDBC讀取和寫入數據庫中的數據

上面兩種讀取和寫入數據的方式其實並不經常用,用的最多的情況一般就是有一堆常年不用的數據放在磁盤太佔空間,於是你的項目經理叫你把數據壓縮一下,壓縮成.parquet格式的文件存起來提高磁盤利用率;再然後,你的老闆或產品經理過了很長一段時間突然提了一個需求,而這個需求需要用到這堆壓縮後的文件,這個時候纔會使用到上面的兩種方式。

用的最多的一般是讀取 Hive 中的數據和通過 Jdbc 讀取數據庫中的數據來解決需求。

1. 通過jdbc讀取mysql中的數據

事先在名爲 test 的數據庫中創建一張名爲 mydata 的表,mydata 有兩列:第一列字段名爲 name,類型爲 varchar;第二列字段名爲 score,類型爲 int。如下:
在這裏插入圖片描述
讀取 mysql 中數據的 spark 的代碼如下:

import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession

object JdbcDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("JdbcDemo").setMaster("local[*]")
    val spark = SparkSession.builder().config(conf).getOrCreate()
    val sc = spark.sparkContext

	// 有四個選項,分別是url(數據庫連接),dbtable(數據庫中表的名),user(用戶名),password(密碼)
    val mysqlDF = spark.read.format("jdbc").
      option("url", "jdbc:mysql://127.0.0.1:3306/test").
      option("dbtable", "mydata").
      option("user", "root").
      option("password", "123").
      load

    mysqlDF.show
  }
}

運行程序,結果如下:
在這裏插入圖片描述

2. 通過jdbc向mysql中寫入數據

在上面代碼的基礎上,添加如下代碼:

// mysqlDF的數據類型是DataFrame類型的
mysqlDF.write.format("jdbc").
  option("url", "jdbc:mysql://127.0.0.1:3306/test").
  option("dbtable", "mydata").
  option("user", "root").
  option("password", "123").
  mode(SaveMode.Append).
  save()

再次運行程序結果如下:
在這裏插入圖片描述
原來的 mydata 表中又追加了一遍原來的數據。

四、Hive中的數據

通過 SparkSQL 讀取 Hive 中的數據,是企業中進行大數據開發最常用的方式。我們的數據一般是存儲在像 Hive 這樣的數據倉庫中的。通過 HiveSQL 來查詢數據底層會轉化成一個或多個 MapReduce 程序,而 MapReduce 程序他對數據的計算是基於磁盤的,存在多次的磁盤 IO 操作,效率有點低。所以我們一般通過 Spark Sql 將 Hive 中的數據讀取到內存中來,讓後利用 Spark 的 RDD 計算模型來進行計算,因爲 RDD 是基於內存的,所以使用 SparkSQL 操作 Hive 會大大提高數據的計算速度。

要想使 Spark SQL 能夠讀取到 hive 中的數據,在服務器端我們只需要將以下文件複製到 Spark 根目錄的 conf 目錄下即可:

  • $HIVE_HOME/conf/hive-site.xml
  • $HADOOP_CONF_DIR/core-site.xml
  • $HADOOP_CONF_DIR/hdfs-site.xml

分別是 Hive 的 conf 目錄中的 hive-site.xml,Hadoop 的 etc 目錄下的 hadoop 目錄中的 core-site.xml 和 hdfs-site.xml 這三個文件。

然後我們只需要在代碼中創建 SparkSession 的時候 enableHiveSupport 啓用對 hive 的支持,然後直接調用 SparkSessionsql 方法就可以直接查詢 hive 中的數據了。

但是,我們在本地開發調試的時候該怎麼辦呢?總不能去連線上的 Hive 吧,本地搭建一個 hive 環境又太麻煩。所以,下面將介紹如何在本地開發中模擬 Hive 的環境:

1.向本地hive中寫入數據

先向本地 Hive 中寫入數據,代碼如下(與最開始寫入parquet文件的代碼類似):

package com.qjl

import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.{SaveMode, SparkSession}

import scala.collection.mutable.ArrayBuffer

object HiveDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("HiveDemo").setMaster("local[*]")
    val spark = SparkSession.builder().config(conf).enableHiveSupport().getOrCreate()
    val sc = spark.sparkContext

    // 創建一個可變數組,向數組中添加10個用戶
    val rows = ArrayBuffer[User]()
    for (i <- 0 until 10) {
      rows += User(i, s"name${i}")
    }
    // 將數組轉變成不可變數組
    val rowArray = rows.toArray
    // 使用用戶數組來創建RDD
    val rowRDD = sc.parallelize(rowArray)

    // 導入spark的隱式轉換函數toDF,將RDD轉換成DataFrame
    import spark.implicits._
    // 會在當前項目的根目錄創建一個spark-warehouse文件夾,裏面會有一個user文件夾,裏面會生成.parquet文件

    rowRDD.toDF().write.mode(SaveMode.Overwrite).saveAsTable("user")
  }
}

/**
 * 用戶樣本類
 * @param user_id 用戶id
 * @param user_name 用戶名稱
 */
case class User(user_id: Int, user_name: String)

注:不同點只是在創建 SparkSession 的時候多調用了一個方法 enableHiveSupport 啓用對 hive 的支持,同時還需要在 pom 文件中加上對 hive 依賴的 jar 包,在文章的末尾有。

運行程序,項目的根目錄產生如下文件:
在這裏插入圖片描述
在 enableHiveSupport 啓用 Spark 對 Hive 的支持後,運行 Spark SQL 程序會在項目的根目錄多了一個 metastore_db 目錄和 derby.log 文件,spark-warehouse 這個目錄原來就有。

我們在搭建生產環境的 Hive 時,有兩種模式:

  • 基於 derby 本地數據庫的本地模式,即表的元數據(字段名,字段類型)存儲在本地的一個目錄:metastore_db
  • 基於 mysql 的遠程模式,即表的元數據(字段名,字段類型)存儲在 mysql 數據庫中

通過上圖中產生的文件我們可以知道 Spark 使用本地 Hive 的原理其實就是使用的基於 derby 本地數據庫的模式,將元數據存儲在本地項目的根目錄下 => metastore_db,derby.log 中存儲 derby 本地數據的日誌信息。

OK,至此爲止,本地的 Hive 中已經有數據了,那麼我們該如何讀取本地 Hive 中的數據呢?難道要像讀取 parquet 文件那樣 spark.read.load?

2. 讀取本地hive中的數據

直接調用 spark.sql("select * from ooxx").show 即可,代碼如下:

import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.{SparkSession}


object HiveDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("HiveDemo").setMaster("local[*]")
    // 一定要啓用對 hive 的支持
    val spark = SparkSession.builder().config(conf).enableHiveSupport().getOrCreate()
    val sc = spark.sparkContext

	// 直接調用者一行代碼就可以讀取本地 hive 中的數據
    spark.sql("select * from user").show
  }
}

運行結果如下:

在這裏插入圖片描述
成功讀取到了本地 Hive 中的數據。

注意:如果在沒有 enableHiveSupport 的情況下 saveAsTable,因爲沒有生成表的元信息,所以即使在 enableHiveSupport 之後通過 spark.sql 讀取數據也是讀取不到的。

正確的順序就是:先 enableHiveSupport 開啓 Spark 對 Hive 的支持,模擬一堆測試數據寫入本地 Hive,然後再通過 spark.sql 讀取數據。

所需的pom依賴如下:

<dependencies>
	<!-- spark核心包 -->
  <dependency>
       <groupId>org.apache.spark</groupId>
       <artifactId>spark-core_2.11</artifactId>
       <version>2.1.1</version>
   </dependency>
   <!-- spark sql所需jar包 -->
   <dependency>
       <groupId>org.apache.spark</groupId>
       <artifactId>spark-sql_2.11</artifactId>
       <version>2.1.1</version>
   </dependency>
   <!-- spark整合hive所需jar包 -->
   <dependency>
       <groupId>org.apache.spark</groupId>
       <artifactId>spark-hive_2.11</artifactId>
       <version>2.1.1</version>
   </dependency>
   <!-- mysql驅動 -->
   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
   </dependency>
</dependencies>

關注我的微信公衆號(曲健磊的個人隨筆),獲取更多大數據乾貨:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章