Cris 的 Spark SQL 筆記

Cris就是我發表於2018-12-30

一、Spark SQL 概述

1.1 什麼是Spark SQL

Spark SQLSpark 用來處理結構化資料的一個模組,它提供了2個程式設計抽象:

DataFrameDataSet,並且作為分散式 SQL 查詢引擎的作用。

Hive 作為對比,Hive 是將 Hive SQL 轉換成 MapReduce 然後提交到叢集上執行,大大簡化了編寫 MapReduce 的程式的複雜性,由於 MapReduce 這種計算模型執行效率比較慢。所有Spark SQL 的應運而生,它是將 Spark SQL 轉換成 RDD,然後提交到叢集執行,執行效率非常快。

1.2 Spark SQL 的特點

這裡引用 Spark 官網

① 易整合

Cris 的 Spark SQL 筆記

② 統一的資料訪問方式

Cris 的 Spark SQL 筆記

③ 相容Hive

Cris 的 Spark SQL 筆記

④ 標準的資料連線

Cris 的 Spark SQL 筆記

1.3 什麼是 DataFrame

Spark 中,DataFrame 是一種以 RDD 為基礎的分散式資料集,類似於傳統資料庫中的二維表格。

DataFrameRDD 的主要區別在於,前者帶有 schema 元資訊,即 DataFrame 所表示的二維表資料集的每一列都帶有名稱和型別。這使得 Spark SQL 得以洞察更多的結構資訊,從而對藏於DataFrame 背後的資料來源以及作用於 DataFrame 之上的變換進行了針對性的優化,最終達到大幅提升執行時效率的目標。

反觀 RDD,由於無從得知所存二維資料的內部結構,Spark Core 只能在 stage 層面進行簡單、通用的流水線優化。

圖示

Cris 的 Spark SQL 筆記

DataFrame 也是懶執行的,但效能上比 RDD 要高,主要原因:

優化的執行計劃,即查詢計劃通過 Spark Catalyst Optimiser 進行優化。比如下面一個例子:

Cris 的 Spark SQL 筆記

看看 Spark CoreSpark SQL 模組對這個計劃的執行步驟:

Cris 的 Spark SQL 筆記

1.4 什麼是 DataSet

DataSet 也是分散式資料集合。

DataSetSpark 1.6 中新增的一個新抽象,是 DataFrame 的一個擴充套件。它提供了 RDD 的優勢(強型別,使用強大的 Lambda 函式的能力)以及 Spark SQL 優化執行引擎的優點,DataSet 也可以使用功能性的轉換(操作 mapflatMapfilter 等等)。

具體的優勢如下:

1)是 DataFrame API 的一個擴充套件,SparkSQL 最新的資料抽象;

2)使用者友好的 API 風格,既具有型別安全檢查也具有 DataFrame 的查詢優化特性;

3)用樣例類來對 DataSet 中定義資料的結構資訊,樣例類中每個屬性的名稱直接對映到 DataSet 中的欄位名稱;

4)DataSet 是強型別的。比如可以有 DataSet[Car]DataSet[Person]

二、Spark SQL 程式設計

2.1 SparkSession

在老的版本中,Spark SQL 提供兩種 SQL 查詢起始點:一個叫 SQLContext,用於 Spark 自己提供的 SQL 查詢;一個叫 HiveContext,用於連線 Hive 的查詢。

SparkSessionSpark 最新的 SQL 查詢起始點,實質上是 SQLContextHiveContext 的組合,所以在 SQLContexHiveContext 上可用的 APISparkSession 上同樣是可以使用的。

SparkSession 內部封裝了 SparkContext,所以計算實際上是由 SparkContext 完成的。

2.2 DataFrame

1. 建立

Spark SQLSparkSession 是建立 DataFrame 和執行 SQL 的入口,建立 DataFrame 有三種方式

  1. 通過 Spark 的資料來源進行建立;
  2. 從一個存在的 RDD 進行轉換;
  3. 還可以從 Hive Table 進行查詢返回
通過 Spark 的資料來源進行建立
  • 檢視 Spark 資料來源進行建立的檔案格式

Cris 的 Spark SQL 筆記

  • 讀取官網提供的 json 檔案建立 DataFrame

Cris 的 Spark SQL 筆記

  • RDD 轉換(詳見 2.5 節)
  • Hive Table 轉換(詳見 3.3節)

2. SQL 風格語法(主要)

直接通過 SQL 語句對 DataFrame 的資料進行操作

  1. 建立一個 DataFrame

Cris 的 Spark SQL 筆記

  1. DataFrame 建立一個臨時表

建立臨時表的三種方式

Cris 的 Spark SQL 筆記

Cris 的 Spark SQL 筆記

  1. 通過 SQL 語句實現查詢全表

Cris 的 Spark SQL 筆記

注意:普通臨時表是 Session 範圍內的,如果想應用範圍內有效,可以使用全域性臨時表。使用全域性臨時表時需要全路徑訪問,如:global_temp.people

  1. 對於 DataFrame 建立一個全域性表
df.createGlobalTempView("people")
複製程式碼
  1. 通過 SQL 語句實現查詢全表
spark.sql("SELECT * FROM global_temp.people").show()
複製程式碼
 spark.newSession().sql("SELECT * FROM global_temp.people").show()
複製程式碼

以上兩行程式碼的執行效果一致~

3. DSL 風格語法(次要)

使用更為簡潔的語法對 DataFrame 的資料操作

  1. 建立一個 DataFrame(同上)

  2. 檢視 DataFrameSchema 資訊

Cris 的 Spark SQL 筆記

  1. 只檢視 name 列資料

Cris 的 Spark SQL 筆記

  1. 檢視 name 列資料以及 age+1 資料

Cris 的 Spark SQL 筆記

  1. 檢視 age 大於 21 的資料

Cris 的 Spark SQL 筆記

  1. 按照 age 分組,檢視資料條數

Cris 的 Spark SQL 筆記

個人感覺簡單的操作可以使用 DSL,複雜查詢再使用 SQL 是一個很不錯的方案

注意:DSL 方法由 DataFrame 呼叫,而 SQLSparkSession 呼叫

4. RDD 轉換為 DateFrame

注意:如果需要 RDDDF 或者 DS 之間操作,那麼都需要引入 import spark.implicits._ 【spark不是包名,而是 SparkSession 物件的名稱】

前置條件:匯入隱式轉換並建立一個 RDD

Cris 的 Spark SQL 筆記

  1. 通過手動確定轉換

Cris 的 Spark SQL 筆記

Cris 的 Spark SQL 筆記

  1. 通過反射確定(需要用到樣例類)

    • 建立一個樣例類
    case class People(name:String, age:Int)
    複製程式碼
    • 根據樣例類將 RDD 轉換為 DataFrame

Cris 的 Spark SQL 筆記

  1. 通過程式設計方式(瞭解)
  • 匯入所需的型別
 import org.apache.spark.sql.types._
複製程式碼
  • 建立 Schema
val structType: StructType = StructType(StructField("name", StringType) :: StructField("age", IntegerType) :: Nil)
複製程式碼
  • 匯入所需的型別
import org.apache.spark.sql.Row
複製程式碼
  • 根據給定的型別建立二元組 RDD
val data = rdd.map{ x => val para = x.split(",");Row(para(0),para(1).trim.toInt)}
複製程式碼
  • 根據資料及給定的 schema 建立 DataFrame
val dataFrame = spark.createDataFrame(data, structType)
複製程式碼

5. DateFrame 轉換為 RDD

Cris 的 Spark SQL 筆記

2.3 DataSet

DataSet 是具有強型別的資料集合,需要提供對應的型別資訊。

DataSet 的建立可以直接使用 Seq 靜態方法建立 或者 RDD 轉換 或者 DataFrame 轉換

1. 建立

  1. 建立一個樣例類
case class Person(name: String, age: Long)
複製程式碼
  1. 建立 DataSet

Cris 的 Spark SQL 筆記

2. RDD 轉換為 DataSet

Spark SQL 能夠自動將包含有 case 類的 RDD 轉換成 DataFramecase 類定義了 table 的結構,case 類屬性通過反射變成了表的列名。case 類可以包含諸如 Seqs 或者 Array 等複雜的結構。

  1. 建立一個 RDD
  2. 建立一個樣例類
case class Person(name: String, age: Int)
複製程式碼
  1. RDD 轉化為 DataSet

Cris 的 Spark SQL 筆記

3. DataSet 轉換為 RDD

Cris 的 Spark SQL 筆記

2.4 DataFrame與DataSet的互操作

1. DataFrame 轉 Dataset

  1. 建立一個 DateFrame

Cris 的 Spark SQL 筆記

  1. 建立一個樣例類並轉換

Cris 的 Spark SQL 筆記

2. Dataset 轉 DataFrame

  1. 建立一個樣例類(同上)
  2. 建立 DataSet
val ds = Seq(Person("Andy", 32)).toDS()
複製程式碼
  1. DataSet 轉化為 DataFrame
val df = ds.toDF
複製程式碼

使用 as 方法,轉成 Dataset,這在資料型別是 DataFrame 又需要針對各個欄位處理時極為方便。在使用一些特殊的操作時,一定要加上 import spark.implicits._ 不然 toDFtoDS 無法使用。

2.5 RDD,DataFrame,DataSet

Cris 的 Spark SQL 筆記

Spark SQLSpark 為我們提供了兩個新的抽象,分別是 DataFrameDataSet。他們和 RDD 有什麼區別呢?首先從版本的產生上來看:

RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)

如果同樣的資料都給到這三個資料結構,他們分別計算之後,都會給出相同的結果。不同是的他們的執行效率和執行方式。在後期的 Spark 版本中,DataSet 有可能會逐步取代 RDDDataFrame成為唯一的 API 介面。

1. 三者的共性

(1)RDDDataFrameDataset 全都是 spark 平臺下的分散式彈性資料集,為處理超大型資料提供便利;

(2)三者都有惰性機制,在進行建立、轉換,如 map 方法時,不會立即執行,只有在遇到 Actionforeach 時,三者才會開始遍歷運算;

(3)三者有許多共同的函式,如 filter排序 等;

(4)在對 DataFrameDataset 進行操作許多操作都需要這個包:import spark.implicits._(在建立好 SparkSession 物件後儘量直接匯入)

這裡給出關於這三者講解比較深入的文章

2. 三者的轉換

Cris 的 Spark SQL 筆記

2.6 IDEA 建立 Spark SQL 程式

通過一個簡單的案例快速入手如何在 IDEA 上開發 Spark SQL 程式

匯入以下依賴

<dependencies>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
            <version>2.1.1</version>
        </dependency>
</dependencies>
複製程式碼

程式碼實現

object Main2 {
  def main(args: Array[String]): Unit = {
    val session: SparkSession = SparkSession.builder().appName("spark sql").master("local[*]").getOrCreate()
    import session.implicits._

    val dataFrame: DataFrame = session.read.json("/home/cris/people.json")
    //列印
    dataFrame.show()

    //DSL風格:查詢年齡在21歲以上的
    dataFrame.filter($"age" > 21).show()

    //建立臨時表
    dataFrame.createOrReplaceTempView("persons")

    //SQL風格:查詢年齡在21歲以上的
    session.sql("SELECT * FROM persons where age > 21").show()

    //關閉連線
    session.stop()
  }
}
複製程式碼

無法找到主類

如果在執行 Scala 或者是 java 程式中,報無法找到主類執行的異常,可能是專案的結構有問題,將父模組直接移除掉,然後重新匯入父模組即可

Cris 的 Spark SQL 筆記

2.7 使用者自定義函式

1. 使用者自定義 UDF 函式

object MyFunc {
  def main(args: Array[String]): Unit = {
    val session: SparkSession = SparkSession.builder().appName("spark sql").master("local[*]").getOrCreate()

    val dataFrame: DataFrame = session.read.json("/home/cris/people.json")
    /*使用者自定義 UDF 函式*/
    session.udf.register("addName", (x: String) => {
      "cool:" + x
    })

    dataFrame.createOrReplaceTempView("people")

    session.sql("select addName(name),age from people").show()

    session.stop()
  }
}
複製程式碼

結果如下

Cris 的 Spark SQL 筆記

2. 使用者自定義 UDAF 函式

強型別的 Dataset 和弱型別的 DataFrame 都提供了相關的聚合函式, 如 count()countDistinct()avg()max()min()

除此之外,使用者可以設定自己的自定義聚合函式。通過繼承 UserDefinedAggregateFunction 來實現使用者自定義聚合函式

/**
  * 定義自己的 UDAF 函式
  *
  * @author cris
  * @version 1.0
  **/
object MyFunc extends UserDefinedAggregateFunction {
  // 聚合函式輸入引數的資料型別
  override def inputSchema: StructType = StructType(StructField("inputField", LongType) :: Nil)

  // 聚合緩衝區中值得資料型別
  override def bufferSchema: StructType = {
    StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil)
  }

  // 返回值的資料型別
  override def dataType: DataType = DoubleType

  // 對於相同的輸入是否一直返回相同的輸出
  override def deterministic: Boolean = true

  // 初始化
  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    // 工資的總額
    buffer(0) = 0L
    // 員工人數
    buffer(1) = 0L
  }

  // 相同 Executor 間的資料合併
  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    buffer(0) = buffer.getLong(0) + input.getLong(0)
    buffer(1) = buffer.getLong(1) + 1
  }

  // 不同 Executor 間的資料合併
  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
    buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
  }

  // 最終函式計算的返回值
  override def evaluate(buffer: Row): Double = {
    buffer.getLong(0).toDouble / buffer.getLong(1)
  }
}
複製程式碼

測試程式碼

object MyFuncTest2 {
  def main(args: Array[String]): Unit = {
    val session: SparkSession = SparkSession.builder().appName("spark sql").master("local[*]").getOrCreate()

    val dataFrame: DataFrame = session.read.json("/home/cris/employees.json")
    session.udf.register("avg", MyFunc)

    dataFrame.createTempView("emp")

    session.sql("select avg(salary) as avg_sal from emp").show()
    session.stop()

  }
}
複製程式碼

測試如下

Cris 的 Spark SQL 筆記

三、Spark SQL 資料的載入與儲存

3.1 通用載入/儲存方法

1. 載入資料

  • 通過 read 方法直接載入資料
scala> spark.read.
csv  jdbc   json  orc   parquet textFile… …
複製程式碼

注意:載入資料的相關引數需寫到上述方法中。如:textFile 需傳入載入資料的路徑,jdbc 需傳入 JDBC 相關引數

  • format 方法(瞭解)
scala> spark.read.format("…")[.option("…")].load("…")
複製程式碼

用法詳解:

(1)format("…"):指定載入的資料型別,包括"csv"、"jdbc"、"json"、"orc"、"parquet"和"textFile"。

(2)load("…"):在"csv"、"orc"、"parquet"和"textFile"格式下需要傳入載入資料的路徑。

(3)option("…"):在"jdbc"格式下需要傳入JDBC相應引數,url、user、password和dbtable

2. 儲存資料

  • write 直接儲存資料
scala> df.write.
csv  jdbc   json  orc   parquet textFile… …
複製程式碼

注意:儲存資料的相關引數需寫到上述方法中。如:textFile 需傳入載入資料的路徑,jdbc 需傳入 JDBC 相關引數

  • format 指定儲存資料型別(瞭解)
scala> df.write.format("…")[.option("…")].save("…")
複製程式碼

用法詳解:

(1)format("…"):指定儲存的資料型別,包括"csv"、"jdbc"、"json"、"orc"、"parquet"和"textFile"。

(2)save ("…"):在"csv"、"orc"、"parquet"和"textFile"格式下需要傳入儲存資料的路徑。

(3)option("…"):在"jdbc"格式下需要傳入JDBC相應引數,url、user、password和dbtable

3. 最佳示例程式碼

object Main2 {
  def main(args: Array[String]): Unit = {
    val session: SparkSession = SparkSession.builder().appName("spark sql").master("local[*]").getOrCreate()

    val dataFrame: DataFrame = session.read.json("/home/cris/people.json")

    //建立臨時表
    dataFrame.createOrReplaceTempView("persons")

    //SQL風格:查詢年齡在21歲以上的
    val frame: DataFrame = session.sql("SELECT * FROM persons where age > 21")
    frame.show()
    frame.write.json("/home/cris/output")

    //關閉連線
    session.stop()
  }
}
複製程式碼

執行效果

Cris 的 Spark SQL 筆記

4. 檔案儲存選項

可以採用SaveMode執行儲存操作,SaveMode 定義了對資料的處理模式。SaveMode 是一個列舉類,其中的常量包括:

(1)Append:當儲存路徑或者表已存在時,追加內容;

(2)Overwrite: 當儲存路徑或者表已存在時,覆寫內容;

(3)ErrorIfExists:當儲存路徑或者表已存在時,報錯;

(4)Ignore:當儲存路徑或者表已存在時,忽略當前的儲存操作

使用如下

df.write.mode(SaveMode.Append).save("… …")
複製程式碼

記得儲存選項放在 save 操作之前執行

5. 預設資料來源

Spark SQL 的預設資料來源為 Parquet 格式。資料來源為 Parquet 檔案時,Spark SQL 可以方便的執行所有的操作。修改配置項 spark.sql.sources.default,可修改預設資料來源格式。

  1. 載入資料
val df = spark.read.load("./examples/src/main/resources/users.parquet")
複製程式碼
  1. 儲存資料
df.select("name", " color").write.save("user.parquet")
複製程式碼

3.2 JSON 檔案

Spark SQL 能夠自動推測 JSON 資料集的結構,並將它載入為一個 Dataset[Row]. 可以通過 SparkSession.read.json() 去載入一個 一個 JSON 檔案。

注意:這個JSON檔案不是一個傳統的JSON檔案,每一行都得是一個JSON串。格式如下:

{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}
複製程式碼

Spark-Shell 操作如下:

  1. 匯入隱式轉換
import spark.implicits._
複製程式碼
  1. 載入 JSON 檔案
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)
複製程式碼
  1. 建立臨時表
peopleDF.createOrReplaceTempView("people")
複製程式碼
  1. 資料查詢
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
+------+
|  name|
+------+
|Justin|
+------+
複製程式碼

3.3 MySQL

Spark SQL 可以通過 JDBC 從關係型資料庫中讀取資料的方式建立 DataFrame,通過對 DataFrame 一系列的計算後,還可以將資料再寫回關係型資料庫中。

可在啟動 shell 時指定相關的資料庫驅動路徑,或者將相關的資料庫驅動放到 Spark 的類路徑下(推薦)。

以 Spark-Shell 為例

  1. 啟動 Spark-Shell
[cris@hadoop101 spark-local]$ bin/spark-shell --master spark://hadoop101:7077 [--jars mysql-connector-java-5.1.27-bin.jar]
複製程式碼

建議將 MySQL 的驅動直接放入到 Spark 的類(jars)路徑下,就不用每次進入 Spark-Shell 帶上 --jar 引數了

  1. 定義 JDBC 相關引數配置資訊
val connectionProperties = new Properties()
connectionProperties.put("user", "root")
connectionProperties.put("password", "000000")
複製程式碼
  1. 使用 read.jdbc 載入引數
val jdbcDF2 = spark.read.jdbc("jdbc:mysql://hadoop102:3306/spark", "person", connectionProperties)
複製程式碼
  1. 或者使用 format 形式載入配置引數(不推薦)
val jdbcDF = spark.read.format("jdbc").option("url", "jdbc:mysql://hadoop102:3306/spark").option("dbtable", " person").option("user", "root").option("password", "000000").load()
複製程式碼
  1. 使用 write.jdbc 儲存資料(可以使用檔案儲存選項)
jdbcDF2.write.mode(org.apache.spark.sql.SaveMode.Append).jdbc("jdbc:mysql://hadoop102:3306/spark", "person", connectionProperties)
複製程式碼
  1. 或者使用 format 形式儲存資料(不推薦)
jdbcDF.write
.format("jdbc")
.option("url", "jdbc:mysql://hadoop102:3306/spark")
.option("dbtable", "person")
.option("user", "root")
.option("password", "000000")
.save()
複製程式碼

以 IDEA 操作為例

  1. pom.xml 匯入 MySQL 驅動依賴
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
複製程式碼
  1. MySQL 表資料

Cris 的 Spark SQL 筆記

  1. IDEA 操作程式碼如下
/**
  * IDEA 測試 Spark SQL 連線遠端的 MySQL 獲取資料和寫入資料
  *
  * @author cris
  * @version 1.0
  **/
object MysqlTest {
  def main(args: Array[String]): Unit = {
    // 獲取 SparkSession
    val session: SparkSession = SparkSession.builder().appName("spark sql").master("local[*]").getOrCreate()

    // 設定配置引數
    val properties = new Properties()
    properties.put("user", "root")
    properties.put("password", "000000")

    // 從 MySQL 獲取資料,show() 方法實際呼叫的是 show(20),預設顯示 20 行資料
    val dataFrame: DataFrame = session.read.jdbc("jdbc:mysql://hadoop102:3306/spark?characterEncoding=UTF-8", "person", properties)
    dataFrame.show()

    // 修改並儲存資料到 MySQL
    dataFrame.write.mode(SaveMode.Append).jdbc("jdbc:mysql://hadoop102:3306/spark?characterEncoding=UTF-8", "person", properties)

    session.stop()
  }
}
複製程式碼

注意:防止中文亂碼,url 加上 ?characterEncoding=UTF-8 ;寫入資料最好指定儲存模式 SaveMode

測試如下:

Cris 的 Spark SQL 筆記

Cris 的 Spark SQL 筆記

3.4 Hive

Apache HiveHadoop 上的 SQL 引擎,Spark SQL 編譯時可以包含 Hive 支援,也可以不包含。包含 Hive 支援的 Spark SQL 可以支援 Hive 表訪問、UDF(使用者自定義函式)以及 Hive 查詢語言(HQL)等。Spark-Shell 預設是Hive支援的;程式碼中是預設不支援的,需要手動指定(加一個引數即可)。

內建 Hive (瞭解)

如果要使用內嵌的 Hive,直接用就可以了。

  • 簡單建立表

Cris 的 Spark SQL 筆記

指定路徑下就會生成該表的資料夾

Cris 的 Spark SQL 筆記

  • 匯入檔案為表資料

在當前 Spark-local 路徑下,建立檔案 bb

1
2
3
4
5
複製程式碼

然後建立表,匯入資料

Cris 的 Spark SQL 筆記

查詢也沒有問題

Cris 的 Spark SQL 筆記

Cris 的 Spark SQL 筆記

對應目錄下也生成了 bb 表的資料夾

Cris 的 Spark SQL 筆記

外接 Hive(重要)

如果想連線外部已經部署好的 Hive,需要通過以下幾個步驟:

  1. Hive 中的 hive-site.xml 拷貝或者軟連線到 Spark 安裝目錄下的 conf 目錄下
[cris@hadoop101 spark-local]$ cp /opt/module/hive-1.2.1/conf/hive-site.xml ./conf/
複製程式碼
  1. JDBC 的驅動包放置在 Spark.jars 目錄下,啟動 Spark-Shell
[cris@hadoop101 spark-local]$ cp /opt/module/mysql-libs/mysql-connector-java-5.1.27/mysql-connector-java-5.1.27-bin.jar ./jars/
複製程式碼

可以通過 Hive 的客戶端建立一張表 users

hive> create table users(id int, name string) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
複製程式碼

並匯入資料

hive> load data local inpath './user.txt' into users;
複製程式碼

此時 HDFS 顯示資料匯入成功

Cris 的 Spark SQL 筆記

Spark-Shell 視窗檢視

Cris 的 Spark SQL 筆記

執行 Hive 的查詢語句

Cris 的 Spark SQL 筆記

可以在 Spark-Shell 執行所有的 Hive 語句,並且執行流程走的是 Spark,而不是 MapReduce

執行Spark SQL CLI

Spark SQL CLI 可以很方便的在本地執行 Hive 後設資料服務以及從命令列執行查詢任務。在 Spark 目錄下執行如下命令啟動 Spark SQL CLI,直接執行 SQL 語句,類似一個 Hive 視窗。

[cris@hadoop101 spark-local]$ bin/spark-sql
複製程式碼

如果使用這種模式進行測試,最好將 log4j 的日誌級別設定為 error,否則會有很多 info 級別的日誌輸出

Cris 的 Spark SQL 筆記

IDEA 測試 Spark 和 Hive 配合(重要)

首先 pom.xml 引入 Hive 依賴

        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-hive_2.11</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-exec</artifactId>
            <version>1.2.1</version>
        </dependency>
複製程式碼

然後將 Hive 的配置檔案 hive-site.xml 放入 resource 路徑下

Cris 的 Spark SQL 筆記

hive-site.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <property>
        <name>javax.jdo.option.ConnectionURL</name>
        <value>jdbc:mysql://hadoop102:3306/metastore?createDatabaseIfNotExist=true</value>
        <description>JDBC connect string for a JDBC metastore</description>
    </property>

    <property>
        <name>javax.jdo.option.ConnectionDriverName</name>
        <value>com.mysql.jdbc.Driver</value>
        <description>Driver class name for a JDBC metastore</description>
    </property>

    <property>
        <name>javax.jdo.option.ConnectionUserName</name>
        <value>root</value>
        <description>username to use against metastore database</description>
    </property>

    <property>
        <name>javax.jdo.option.ConnectionPassword</name>
        <value>000000</value>
        <description>password to use against metastore database</description>
    </property>
    <property>
        <name>hive.cli.print.header</name>
        <value>true</value>
    </property>

    <property>
        <name>hive.cli.print.current.db</name>
        <value>true</value>
    </property>

    <property>
        <name>hive.zookeeper.quorum</name>
        <value>hadoop101,hadoop102,hadoop103</value>
        <description>The list of ZooKeeper servers to talk to. This is only needed for read/write locks.</description>
    </property>
    <property>
        <name>hive.zookeeper.client.port</name>
        <value>2181</value>
        <description>The port of ZooKeeper servers to talk to. This is only needed for read/write locks.</description>
    </property>
</configuration>
複製程式碼

具體的配置介紹這裡不再贅述,可以參考我的 Hive 筆記

測試程式碼如下:

/**
  * IDEA 測試 Spark SQL 和 Hive 的聯動
  *
  * @author cris
  * @version 1.0
  **/
object HiveTest {
  def main(args: Array[String]): Unit = {
    // 注意開啟 enableHiveSupport
    val session: SparkSession = SparkSession.builder().enableHiveSupport().appName("spark sql").master("local[*]")
      .getOrCreate()

    session.sql("show tables").show()

    // 注意關閉 session 連線
    session.stop()
  }
}
複製程式碼

執行結果如下

Cris 的 Spark SQL 筆記

正好就是剛才建立的 Hive

IDEA 自動換行設定

CrisIDEA 設定一行字數最多 120,否則就自動換行,大大提高閱讀的舒適感和編碼的規範性

設定參考

Deepin 的 Terminal 右鍵複製

因為 Cris 使用的是 Linux 桌面系統 Deepin,所以經常使用自帶的 Terminal 連線遠端伺服器,這裡給出快速右鍵複製 Terminal 內容的設定

設定參考

Typora 的快捷鍵自定義設定

因為 Cris 之前使用的是 MacBook,輸入法搜狗會很智慧的為輸入的英文進行前後空格分割,換了 Deepin 後,自帶的雖然也是搜狗輸入法,但是沒有對英文自動空格分割的功能了,後面想想辦法,看怎麼解決~

因為要對英文和重要內容進行突出顯示,Typora 中設定 code 的快捷鍵預設為 Ctrl+Shift+`,比較麻煩,網上找了找自定義快捷鍵的設定,最後設定成 Ctrl+C

設定參考

相關文章