一、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 的支持,然後直接調用 SparkSession 的 sql 方法就可以直接查詢 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>
關注我的微信公衆號(曲健磊的個人隨筆),獲取更多大數據乾貨: