大數(shù)據(jù)Hadoop之——Spark SQL+Spark Streaming

一、Spark SQL概述

Spark SQL是Spark用來處理結構化數(shù)據(jù)的一個模塊,它提供了兩個編程抽象叫做DataFrame和DataSet并且作為分布式SQL查詢引擎的作用,其實也是對RDD的再封裝。大數(shù)據(jù)Hadoop之——計算引擎Spark,官方文檔:https://spark.apache.org/sql/

二、SparkSQL版本

1)SparkSQL的演變之路

1.png
  • 1.0以前: Shark(入口:SQLContext和HiveContext)

    1. SQLContext:主要DataFrame的構建以及DataFrame的執(zhí)行,SQLContext指的是spark中SQL模塊的程序入口。
    2. HiveContext:是SQLContext的子類,專門用于與Hive的集成,比如讀取Hive的元數(shù)據(jù),數(shù)據(jù)存儲到Hive表、Hive的窗口分析函數(shù)等。
  • 1.1.x開始:SparkSQL(只是測試性的)

  • 1.3.x: SparkSQL(正式版本)+Dataframe

  • 1.5.x: SparkSQL 鎢絲計劃

  • 1.6.x: SparkSQL+DataFrame+DataSet(測試版本)

  • 2.x:

    1. 入口:SparkSession(spark應用程序的一個整體入口),合并了SQLContext和HiveContext
    2. SparkSQL+DataFrame+DataSet(正式版本)
    3. Spark Streaming-》Structured Streaming(DataSet)

2)shark與SparkSQL對比

  • shark
    1. 執(zhí)行計劃優(yōu)化完全依賴于Hive,不方便添加新的優(yōu)化策略;
    2. Spark是線程級并行,而MapReduce是進程級并行。
    3. Spark在兼容Hive的實現(xiàn)上存在線程安全問題,導致Shark
      不得不使用另外一套獨立維護的打了補丁的Hive源碼分支;
  • Spark SQL
    1. 作為Spark生態(tài)的一員繼續(xù)發(fā)展,而不再受限于Hive,
    2. 只是兼容Hive;Hive on Spark作為Hive的底層引擎之一
    3. Hive可以采用Map-Reduce、Tez、Spark等引擎

3)SparkSession

  • SparkSession是Spark 2.0引如的新概念。SparkSession為用戶提供了統(tǒng)一的切入點,來讓用戶學習spark的各項功能。
  • 在spark的早期版本中,SparkContext是spark的主要切入點,由于RDD是主要的API,我們通過sparkcontext來創(chuàng)建和操作RDD。對于每個其他的API,我們需要使用不同的context。

【例如】對于Streming,我們需要使用StreamingContext;對于sql,使用sqlContext;對于Hive,使用hiveContext。但是隨著DataSet和DataFrame的API逐漸成為標準的API,就需要為他們建立接入點。所以在spark2.0中,引入SparkSession作為DataSet和DataFrame API的切入點,SparkSession封裝了SparkConf、SparkContext和SQLContext。為了向后兼容,SQLContext和HiveContext也被保存下來。

  • SparkSession實質上是SQLContext和HiveContext的組合(未來可能還會加上StreamingContext),所以在SQLContext和HiveContext上可用的API在SparkSession上同樣是可以使用的。SparkSession內部封裝了sparkContext,所以計算實際上是由sparkContext完成的,在spark 2.x中不推薦使用SparkContext對象讀取數(shù)據(jù),而是推薦SparkSession

三、RDD、DataFrames和DataSet

1)三者關聯(lián)關系

DataFrame 和 DataSet 是 Spark SQL 提供的基于 RDD 的結構化數(shù)據(jù)抽象。它既有 RDD 不可變、分區(qū)、存儲依賴關系等特性,又擁有類似于關系型數(shù)據(jù)庫的結構化信息。所以,基于 DataFrame 和 DataSet API 開發(fā)出的程序會被自動優(yōu)化,使得開發(fā)人員不需要操作底層的 RDD API 來進行手動優(yōu)化,大大提升開發(fā)效率。但是 RDD API 對于非結構化的數(shù)據(jù)處理有獨特的優(yōu)勢,比如文本流數(shù)據(jù),而且更方便我們做底層的操作

2.png

3.png

1)RDD

RDD(Resilient Distributed Dataset)叫做彈性分布式數(shù)據(jù)集,是Spark中最基本的數(shù)據(jù)抽象,它代表一個不可變、可分區(qū)、里面的元素可并行計算的集合。RDD具有數(shù)據(jù)流模型的特點:自動容錯、位置感知性調度和可伸縮性。RDD允許用戶在執(zhí)行多個查詢時顯式地將工作集緩存在內存中,后續(xù)的查詢能夠重用工作集,這極大地提升了查詢速度。

1、核心概念

  • 一組分片(Partition):即數(shù)據(jù)集的基本組成單位。對于RDD來說,每個分片都會被一個計算任務處理,并決定并行計算的粒度。用戶可以在創(chuàng)建RDD時指定RDD的分片個數(shù),如果沒有指定,那么就會采用默認值。默認值就是程序所分配到的CPU Core的數(shù)目。

  • 一個計算每個分區(qū)的函數(shù)。Spark中RDD的計算是以分片為單位的,每個RDD都會實現(xiàn)compute函數(shù)以達到這個目的。compute函數(shù)會對迭代器進行復合,不需要保存每次計算的結果。

  • RDD之間的依賴關系:RDD的每次轉換都會生成一個新的RDD,所以RDD之間就會形成類似于流水線一樣的前后依賴關系。在部分分區(qū)數(shù)據(jù)丟失時,Spark可以通過這個依賴關系重新計算丟失的分區(qū)數(shù)據(jù),而不是對RDD的所有分區(qū)進行重新計算。

  • 一個Partitioner:即RDD的分片函數(shù)。當前Spark中實現(xiàn)了兩種類型的分片函數(shù),一個是基于哈希的HashPartitioner,另外一個是基于范圍的RangePartitioner。只有對于于key-value的RDD,才會有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函數(shù)不但決定了RDD本身的分片數(shù)量,也決定了parent RDD Shuffle輸出時的分片數(shù)量。

  • 一個列表:存儲存取每個Partition的優(yōu)先位置(preferred location)。對于一個HDFS文件來說,這個列表保存的就是每個Partition所在的塊的位置。按照“移動數(shù)據(jù)不如移動計算”的理念,Spark在進行任務調度的時候,會盡可能地將計算任務分配到其所要處理數(shù)據(jù)塊的存儲位置。

2、RDD簡單操作

啟動spark-shell,其實spark-shell低層也是調用spark-submit,首先需要配置好,當然也可以寫在命令行,但是不推薦。配置如下,僅供參考(這里使用yarn模式):

$ cat spark-defaults.conf
4.png

啟動spark-shell(下面會詳解講解)

$ spark-shell
5.png

\color{red}{【問題】發(fā)現(xiàn)有個WARN:WARN yarn.Client: Neither spark.yarn.jars nor spark.yarn.archive is set, falling back to uploading libraries under SPARK_HOME.}
【原因】是因為Spark提交任務到y(tǒng)arn集群,需要上傳相關spark的jar包到HDFS。
【解決】 提前上傳到HDFS集群,并且在Spark配置文件指定文件路徑,就可以避免每次提交任務到Yarn都需要重復上傳文件。下面是解決的具體操作步驟:

### 打包jars,jar相關的參數(shù)說明
#-c  創(chuàng)建一個jar包
# -t 顯示jar中的內容列表
#-x 解壓jar包
#-u 添加文件到jar包中
#-f 指定jar包的文件名
#-v  生成詳細的報造,并輸出至標準設備
#-m 指定manifest.mf文件.(manifest.mf文件中可以對jar包及其中的內容作一些一設置)
#-0 產生jar包時不對其中的內容進行壓縮處理
#-M 不產生所有文件的清單文件(Manifest.mf)。這個參數(shù)與忽略掉-m參數(shù)的設置
#-i    為指定的jar文件創(chuàng)建索引文件
#-C 表示轉到相應的目錄下執(zhí)行jar命令,相當于cd到那個目錄,然后不帶-C執(zhí)行jar命令
$ cd /opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2
$ jar cv0f spark-libs.jar -C ./jars/ .
$ ll
### 在hdfs上創(chuàng)建存放jar包目錄
$ hdfs dfs -mkdir -p /spark/jars
## 上傳jars到HDFS
$ hdfs dfs -put spark-libs.jar /spark/jars/
## 增加配置spark-defaults.conf 
spark.yarn.archive=hdfs:///spark/jars/spark-libs.jar

然后再啟動spark-shell

在Spark Shell中,有一個專有的SparkContext已經為您創(chuàng)建好了,變量名叫做sc,自己創(chuàng)建的SparkContext將無法工作。

$ spark-shell
6.png
### 由一個已經存在的Scala集合創(chuàng)建。
val array = Array(1,2,3,4,5)
# spark使用parallelize方法創(chuàng)建RDD
val rdd = sc.parallelize(array)
7.png

這里只是簡單的創(chuàng)建RDD操作,后面會有更多RDD相關的演示操作。

3、RDD API

Spark支持兩個類型(算子)操作:Transformation和Action

1)Transformation

主要做的是就是將一個已有的RDD生成另外一個RDD。Transformation具有l(wèi)azy特性(延遲加載)。Transformation算子的代碼不會真正被執(zhí)行。只有當我們的程序里面遇到一個action算子的時候,代碼才會真正的被執(zhí)行。這種設計讓Spark更加有效率地運行。

常用的Transformation:

轉換 含義
map(func) 返回一個新的RDD,該RDD由每一個輸入元素經過func函數(shù)轉換后組成
filter(func) 返回一個新的RDD,該RDD由經過func函數(shù)計算后返回值為true的輸入元素組成
flatMap(func) 類似于map,但是每一個輸入元素可以被映射為0或多個輸出元素(所以func應該返回一個序列,而不是單一元素)
mapPartitions(func) 類似于map,但獨立地在RDD的每一個分片上運行,因此在類型為T的RDD上運行時,func的函數(shù)類型必須是Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func) 類似于mapPartitions,但func帶有一個整數(shù)參數(shù)表示分片的索引值,因此在類型為T的RDD上運行時,func的函數(shù)類型必須是(Int, Interator[T]) => Iterator[U]
sample(withReplacement, fraction, seed) 根據(jù)fraction指定的比例對數(shù)據(jù)進行采樣,可以選擇是否使用隨機數(shù)進行替換,seed用于指定隨機數(shù)生成器種子
union(otherDataset) 對源RDD和參數(shù)RDD求并集后返回一個新的RDD
intersection(otherDataset) 對源RDD和參數(shù)RDD求交集后返回一個新的RDD
distinct([numTasks])) 對源RDD進行去重后返回一個新的RDD
groupByKey([numTasks]) 在一個(K,V)的RDD上調用,返回一個(K, Iterator[V])的RDD
reduceByKey(func, [numTasks]) 在一個(K,V)的RDD上調用,返回一個(K,V)的RDD,使用指定的reduce函數(shù),將相同key的值聚合到一起,與groupByKey類似,reduce任務的個數(shù)可以通過第二個可選的參數(shù)來設置
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) 先按分區(qū)聚合 再總的聚合 每次要跟初始值交流 例如:aggregateByKey(0)(+,+) 對k/y的RDD進行操作
sortByKey([ascending], [numTasks]) 在一個(K,V)的RDD上調用,K必須實現(xiàn)Ordered接口,返回一個按照key進行排序的(K,V)的RDD
sortBy(func,[ascending], [numTasks]) 與sortByKey類似,但是更靈活 第一個參數(shù)是根據(jù)什么排序 第二個是怎么排序 false倒序 第三個排序后分區(qū)數(shù) 默認與原RDD一樣
join(otherDataset, [numTasks]) 在類型為(K,V)和(K,W)的RDD上調用,返回一個相同key對應的所有元素對在一起的(K,(V,W))的RDD 相當于內連接(求交集)
cogroup(otherDataset, [numTasks]) 在類型為(K,V)和(K,W)的RDD上調用,返回一個(K,(Iterable<V>,Iterable<W>))類型的RDD
cartesian(otherDataset) 兩個RDD的笛卡爾積 的成很多個K/V
pipe(command, [envVars]) 調用外部程序
coalesce(numPartitions) 重新分區(qū) 第一個參數(shù)是要分多少區(qū),第二個參數(shù)是否shuffle 默認false 少分區(qū)變多分區(qū) true 多分區(qū)變少分區(qū) false
repartition(numPartitions)
重新分區(qū) 必須shuffle 參數(shù)是要分多少區(qū) 少變多
repartitionAndSortWithinPartitions(partitioner) 重新分區(qū)+排序 比先分區(qū)再排序效率高 對K/V的RDD進行操作
foldByKey(zeroValue)(seqOp) 該函數(shù)用于K/V做折疊,合并處理 ,與aggregate類似 第一個括號的參數(shù)應用于每個V值 第二括號函數(shù)是聚合例如:+
combineByKey 合并相同的key的值 rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)
partitionBy(partitioner) 對RDD進行分區(qū) partitioner是分區(qū)器 例如new HashPartition(2)
cache/persist RDD緩存,可以避免重復計算從而減少時間,區(qū)別:cache內部調用了persist算子,cache默認就一個緩存級別MEMORY-ONLY ,而persist則可以選擇緩存級別
Subtract(rdd) 返回前rdd元素不在后rdd的rdd
leftOuterJoin leftOuterJoin類似于SQL中的左外關聯(lián)left outer join,返回結果以前面的RDD為主,關聯(lián)不上的記錄為空。只能用于兩個RDD之間的關聯(lián),如果要多個RDD關聯(lián),多關聯(lián)幾次即可。
rightOuterJoin rightOuterJoin類似于SQL中的有外關聯(lián)right outer join,返回結果以參數(shù)中的RDD為主,關聯(lián)不上的記錄為空。只能用于兩個RDD之間的關聯(lián),如果要多個RDD關聯(lián),多關聯(lián)幾次即可
subtractByKey substractByKey和基本轉換操作中的subtract類似只不過這里是針對K的,返回在主RDD中出現(xiàn),并且不在otherRDD中出現(xiàn)的元素
2)Action

觸發(fā)代碼的運行,我們一段spark代碼里面至少需要有一個action操作。

常用的Action:

動作 含義
reduce(func) 通過func函數(shù)聚集RDD中的所有元素,這個功能必須是課交換且可并聯(lián)的
collect() 在驅動程序中,以數(shù)組的形式返回數(shù)據(jù)集的所有元素
count() 返回RDD的元素個數(shù)
first() 返回RDD的第一個元素(類似于take(1))
take(n) 返回一個由數(shù)據(jù)集的前n個元素組成的數(shù)組
takeSample(withReplacement,num, [seed]) 返回一個數(shù)組,該數(shù)組由從數(shù)據(jù)集中隨機采樣的num個元素組成,可以選擇是否用隨機數(shù)替換不足的部分,seed用于指定隨機數(shù)生成器種子
takeOrdered(n, [ordering]) 返回原RDD排序(默認升序排)后,前n個元素組成的數(shù)組
saveAsTextFile(path) 將數(shù)據(jù)集的元素以textfile的形式保存到HDFS文件系統(tǒng)或者其他支持的文件系統(tǒng),對于每個元素,Spark將會調用toString方法,將它裝換為文件中的文本
saveAsSequenceFile(path) 將數(shù)據(jù)集中的元素以Hadoop sequencefile的格式保存到指定的目錄下,可以使HDFS或者其他Hadoop支持的文件系統(tǒng)。
saveAsObjectFile(path) saveAsObjectFile用于將RDD中的元素序列化成對象,存儲到文件中。使用方法和saveAsTextFile類似
countByKey() 針對(K,V)類型的RDD,返回一個(K,Int)的map,表示每一個key對應的元素個數(shù)。
foreach(func) 在數(shù)據(jù)集的每一個元素上,運行函數(shù)func進行更新。
aggregate 先對分區(qū)進行操作,在總體操作
reduceByKeyLocally 返回一個 dict 對象,同樣是將同 key 的元素進行聚合
lookup lookup用于(K,V)類型的RDD,指定K值,返回RDD中該K對應的所有V值。
top top函數(shù)用于從RDD中,按照默認(降序)或者指定的排序規(guī)則,返回前num個元素。
fold fold是aggregate的簡化,將aggregate中的seqOp和combOp使用同一個函數(shù)op。
foreachPartition 遍歷原RDD元素經過func函數(shù)運算過后的結果集,foreachPartition算子分區(qū)操作

4、實戰(zhàn)操作

1、針對各個元素的轉化操作

我們最常用的轉化操作應該是map() 和filter(),轉化操作map() 接收一個函數(shù),把這個函數(shù)用于RDD 中的每個元素,將函數(shù)的返回結果作為結果RDD 中對應元素的值。而轉化操作filter() 則接收一個函數(shù),并將RDD 中滿足該函數(shù)的元素放入新的RDD 中返回。

讓我們看一個簡單的例子,用map() 對RDD 中的所有數(shù)求平方

# 通過parallelize創(chuàng)建RDD對象
val input = sc.parallelize(List(1, 2, 3, 4))
val result = input.map(x => x * x)
println(result.collect().mkString(","))
8.png

2、對一個數(shù)據(jù)為{1,2,3,3}的RDD進行基本的RDD轉化操作(去重)

var rdd = sc.parallelize(List(1,2,3,3))
rdd.distinct().collect().mkString(",")
9.png

3、對數(shù)據(jù)分別為{1,2,3}和{3,4,5}的RDD進行針對兩個RDD的轉化操作

var rdd = sc.parallelize(List(1,2,3))
var other = sc.parallelize(List(3,4,5))
# 生成一個包含兩個RDD中所有元素的RDD
rdd.union(other).collect().mkString(",")
# 求兩個RDD共同的元素RDD
rdd.intersection(other).collect().mkString(",")
10.png

4、行動操作

行動操作reduce(),它接收一個函數(shù)作為參數(shù),這個函數(shù)要操作兩個RDD 的元素類型的數(shù)據(jù)并返回一個同樣類型的新元素。一個簡單的例子就是函數(shù)+,可以用它來對我們的RDD 進行累加。使用reduce(),可以很方便地計算出RDD中所有元素的總和、元素的個數(shù),以及其他類型的聚合操作。

var rdd = sc.parallelize(List(1,2,3,4,5,6,7))
# 求和
var sum = rdd.reduce((x, y) => x + y)
# 求元素個數(shù)
var sum = rdd.count()

# 聚合操作
var rdd = sc.parallelize(List(1,2,3,4,5,6,7))
var result = rdd.aggregate((0,0))((acc,value) => (acc._1 + value,acc._2 + 1),(acc1,acc2) => (acc1._1 + acc2._1 , acc1._2 + acc2._2))
var avg = result._1/result._2.toDouble
11.png

這里只是演示幾個簡單的示例,更多RDD的操作,可以參考官方文檔學習哦。

2)DataFrames

在Spark中,DataFrame提供了一個領域特定語言(DSL)和SQL來操作結構化數(shù)據(jù),DataFrame是一種以RDD為基礎的分布式數(shù)據(jù)集,類似于傳統(tǒng)數(shù)據(jù)庫中的二維表格。

12.png
  • RDD,由于無從得知所存數(shù)據(jù)元素的具體內部結構,Spark Core只能在stage層面進行簡單、通用的流水線優(yōu)化。
  • DataFrame底層是以RDD為基礎的分布式數(shù)據(jù)集,和RDD的主要區(qū)別的是:RDD中沒有schema信息,而DataFrame中數(shù)據(jù)每一行都包含schema。DataFrame = RDD + shcema

1、DSL風格語法操作

1)DataFrame創(chuàng)建

創(chuàng)建DataFrame的兩種基本方式:

  • 已存在的RDD調用toDF()方法轉換得到DataFrame。
  • 通過Spark讀取數(shù)據(jù)源直接創(chuàng)建DataFrame。

直接創(chuàng)建DataFarme對象

若使用SparkSession方式創(chuàng)建DataFrame,可以使用spark.read從不同類型的文件中加載數(shù)據(jù)創(chuàng)建DataFrame。spark.read的具體操作,如下所示。

方法名 描述
spark.read.text(“people.txt”) 讀取txt格式文件,創(chuàng)建DataFrame
spark.read.csv (“people.csv”) 讀取csv格式文件,創(chuàng)建DataFrame
spark.read.text(“people.json”) 讀取json格式文件,創(chuàng)建DataFrame
spark.read.text(“people.parquet”) 讀取parquet格式文件,創(chuàng)建DataFrame

1、在本地創(chuàng)建一個person.txt文本文檔,用于讀取:運行spark-shell:

# person.txt,Name,Age,Height
p1_name,18,165
p2_name,19,170
p3_name,20,188
p4_name,21,190
# 啟動spark shell,默認會創(chuàng)建一個spark名稱的spark session對象
$ spark-shell
# 定義變量,【注意】所有節(jié)點都得創(chuàng)建這個person文件,要不然調度沒有這個文件的機器會報錯
var inputFile = "file:///opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/test/person.txt"
# 讀取本地文件
val personDF = spark.read.text("file:///opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/test/person.txt")
val personDF = spark.read.text(inputFile)
# 顯示
personDF.show()
# 將文件put到hdfs上
# 讀取hdfs文件(推薦)
val psersonDF = spark.read.text("hdfs:///person.txt")
13.png

2、有RDD轉換成DataFrame

動作 含義
show() 查看DataFrame中的具體內容信息
printSchema() 查看DataFrame的Schema信息
select() 查看DataFrame中選取部分列的數(shù)據(jù)及進行重命名
filter() 實現(xiàn)條件查詢,過濾出想要的結果
groupBy() 對記錄進行分組
sort() 對特定字段進行排序操作
toDF() 把RDD數(shù)據(jù)類型轉成DataFarme
# 讀取文本文檔,按逗號分割開來
val lineRDD = sc.textFile("hdfs:///person.txt").map(_.split(","))
case class Person(name:String, age:Int, height:Int)
# 按照樣式類對RDD數(shù)據(jù)進行分割成map
val personRDD = lineRDD.map(x => Person(x(0).toString, x(1).toInt, x(2).toInt))
# 把RDD數(shù)據(jù)類型轉成DataFarme
val personDF = personRDD.toDF()
# 查看這個表
personDF.show()
# 查看Schema數(shù)據(jù)
personDF.printSchema()
# 查看列
personDF.select(personDF.col("name")).show
# 過濾年齡小于25的
personDF.filter(col("age") >= 25).show
14.png

15.png

這里提供常用的spark dataframe方法:

方法名 含義
collect() 返回值是一個數(shù)組,返回dataframe集合所有的行
collectAsList() 返回值是一個java類型的數(shù)組,返回dataframe集合所有的行
count() 返回一個number類型的,返回dataframe集合的行數(shù)
describe(cols: String*) 返回一個通過數(shù)學計算的類表值(count, mean, stddev, min, and max),這個可以傳多個參數(shù),中間用逗號分隔,如果有字段為空,那么不參與運算,只這對數(shù)值類型的字段。例如df.describe("age", "height").show()
first() 返回第一行 ,類型是row類型
head() 返回第一行 ,類型是row類型
head(n:Int) 返回n行 ,類型是row 類型
show() 返回dataframe集合的值 默認是20行,返回類型是unit
show(n:Int) 返回n行,返回值類型是unit
table(n:Int) 返回n行 ,類型是row 類型
cache() 同步數(shù)據(jù)的內存
columns 返回一個string類型的數(shù)組,返回值是所有列的名字
dtypes 返回一個string類型的二維數(shù)組,返回值是所有列的名字以及類型
explan() 打印執(zhí)行計劃 物理的
explain(n:Boolean) 輸入值為 false 或者true ,返回值是unit 默認是false ,如果輸入true 將會打印 邏輯的和物理的
isLocal 返回值是Boolean類型,如果允許模式是local返回true 否則返回false
persist(newlevel:StorageLevel) 返回一個dataframe.this.type 輸入存儲模型類型
printSchema() 打印出字段名稱和類型 按照樹狀結構來打印
registerTempTable(tablename:String) 返回Unit ,將df的對象只放在一張表里面,這個表隨著對象的刪除而刪除了
schema 返回structType 類型,將字段名稱和類型按照結構體類型返回
toDF() 返回一個新的dataframe類型的
toDF(colnames:String*) 將參數(shù)中的幾個字段返回一個新的dataframe類型的
unpersist() 返回dataframe.this.type 類型,去除模式中的數(shù)據(jù)
unpersist(blocking:Boolean) 返回dataframe.this.type類型 true 和unpersist是一樣的作用false 是去除RDD
agg(expers:column*) 返回dataframe類型 ,同數(shù)學計算求值
agg(exprs: Map[String, String]) 返回dataframe類型 ,同數(shù)學計算求值 map類型的
agg(aggExpr: (String, String), aggExprs: (String, String)*) 返回dataframe類型 ,同數(shù)學計算求值
apply(colName: String) 返回column類型,捕獲輸入進去列的對象
as(alias: String) 返回一個新的dataframe類型,就是原來的一個別名
col(colName: String) 返回column類型,捕獲輸入進去列的對象
cube(col1: String, cols: String*) 返回一個GroupedData類型,根據(jù)某些字段來匯總
distinct 去重 返回一個dataframe類型
drop(col: Column) 刪除某列 返回dataframe類型
dropDuplicates(colNames: Array[String]) 刪除相同的列 返回一個dataframe
except(other: DataFrame) 返回一個dataframe,返回在當前集合存在的在其他集合不存在的
filter(conditionExpr: String) 刷選部分數(shù)據(jù),返回dataframe類型
groupBy(col1: String, cols: String*) 根據(jù)某寫字段來匯總返回groupedate類型
intersect(other: DataFrame) 返回一個dataframe,在2個dataframe都存在的元素
join(right: DataFrame, joinExprs: Column, joinType: String) 一個是關聯(lián)的dataframe,第二個關聯(lián)的條件,第三個關聯(lián)的類型:inner, outer, left_outer, right_outer, leftsemi
limit(n: Int) 返回dataframe類型 去n 條數(shù)據(jù)出來
orderBy(sortExprs: Column*) 做alise排序
sort(sortExprs: Column*) 排序 df.sort(df("age").desc).show(); 默認是asc
select(cols:string*) dataframe 做字段的刷選 df.select("colA","colB" + 1)
withColumnRenamed(existingName: String, newName: String) 修改列表 df.withColumnRenamed("name","names").show();
withColumn(colName: String, col: Column) 增加一列 df.withColumn("aa",df("name")).show();

這里已經列出了很多常用方法了,基本上涵蓋了大部分操作,當然也可以參考官方文檔

2、SQL風格語法操作

DataFrame的一個強大之處就是我們可以將它看作是一個關系型數(shù)據(jù)表,然后可以通過在程序中使用spark.sql() 來執(zhí)行SQL查詢,結果將作為一個DataFrame返回。因為spark session包含了Hive Context,所以spark.sql() 會自動啟動連接hive,默認模式就是hive里的local模式(內嵌derby)

啟動spark-shell

$ spark-shell

會在執(zhí)行spark-shell當前目錄下生成兩個文件:derby.log,metastore_db


16.png

接下來就可以happy的寫sql了,這里就演示幾個命令,跟之前的hive一樣,把sql語句放在spark.sql()方法里執(zhí)行即可,不清楚hive sql的可以參考我之前的文章:大數(shù)據(jù)Hadoop之——數(shù)據(jù)倉庫Hive

# 有個默認default庫
$ spark.sql("show databases").show
# 默認當前庫是default
$ spark.sql("show tables").show
17.png

通過spark-sql啟動spark shell

操作就更像sql語法了,已經跟hive差不多了。接下來演示幾個命令,大家就很清楚了。

$ spark-sql
show databases;
create database test007

同樣也會在當前目錄下自動創(chuàng)建兩個文件:derby.log,metastore_db


18.png

3)DataSet

DataSet是分布式的數(shù)據(jù)集合,Dataset提供了強類型支持,也是在RDD的每行數(shù)據(jù)加了類型約束。DataSet是在Spark1.6中添加的新的接口。它集中了RDD的優(yōu)點(強類型和可以用強大lambda函數(shù))以及使用了Spark SQL優(yōu)化的執(zhí)行引擎。DataSet可以通過JVM的對象進行構建,可以用函數(shù)式的轉換(map/flatmap/filter)進行多種操作。

1、通過spark.createDataset通過集合進行創(chuàng)建dataSet

val ds1 = spark.createDataset(1 to 10)
ds1.show
19.png

2、從已經存在的rdd當中構建dataSet

官方文檔

val ds2 = spark.createDataset(sc.textFile("hdfs:////person.txt"))
20.png

3、通過樣例類配合創(chuàng)建DataSet

case class Person(name:String,age:Int)
val personDataList = List(Person("zhangsan",18),Person("lisi",28))
val personDS = personDataList.toDS
personDS.show
21.png

4、通過DataFrame轉化生成
Music.json文件內容如下:

{"name":"上海灘","singer":"葉麗儀","album":"香港電視劇主題歌","path":"mp3/shanghaitan.mp3"}
{"name":"一生何求","singer":"陳百強","album":"香港電視劇主題歌","path":"mp3/shanghaitan.mp3"}
{"name":"紅日","singer":"李克勤","album":"懷舊專輯","path":"mp3/shanghaitan.mp3"}
{"name":"愛如潮水","singer":"張信哲","album":"懷舊專輯","path":"mp3/airucaoshun.mp3"}
{"name":"紅茶館","singer":"陳惠嫻","album":"懷舊專輯","path":"mp3/redteabar.mp3"}

case class Music(name:String,singer:String,album:String,path:String)
# 注意把test.json傳到hdfs上
val jsonDF = spark.read.json("hdfs:///Music.json")
val jsonDS = jsonDF.as[Music]
jsonDS.show
22.png

RDD,DataFrame,DataSet互相轉化

23.png

四、RDD、DataFrame和DataSet的共性與區(qū)別

24.png
  • RDD[Person]:以Person為類型參數(shù),但不了解 其內部結構。

  • DataFrame:提供了詳細的結構信息schema(結構)列的名稱和類型。這樣看起來就像一張表了

  • DataSet[Person]:不光有schema(結構)信息,還有類型信息

1)共性

  • 三者都是spark平臺下的分布式彈性數(shù)據(jù)集,為處理超大型數(shù)據(jù)提供便利
  • 三者都有惰性機制。在創(chuàng)建時、轉換時(如map)不會立即執(zhí)行,只有在遇到action算子的時候(比如foreach),才開始進行觸發(fā)計算。極端情況下,如果代碼中只有創(chuàng)建、轉換,但是沒有在后面的action中使用對應的結果,在執(zhí)行時會被跳過。
  • 三者都有partition的概念,都有緩存(cache)的操作,還可以進行檢查點操作(checkpoint)
  • 三者都有許多共同的函數(shù)(如map、filter,sorted等等)。
    在對DataFrame和DataSet操作的時候,大多數(shù)情況下需要引入隱式轉換(ssc.implicits._)

2)區(qū)別

  • DataFrame:DataFrame是DataSet的特例,也就是說DataSet[Row]的別名;DataFrame = RDD + schema
    1. DataFrame的每一行的固定類型為Row,只有通過解析才能獲得各個字段的值
    2. DataFrame與DataSet通常與spark ml同時使用
    3. DataFrame與DataSet均支持sparkSql操作,比如select,groupby等,也可以注冊成臨時表,進行sql語句操作
    4. DataFrame與DateSet支持一些方便的保存方式,比如csv,可以帶上表頭,這樣每一列的字段名就可以一目了然
  • DataSet:DataSet = RDD + case class
    1. DataSet與DataFrame擁有相同的成員函數(shù),區(qū)別只是只是每一行的數(shù)據(jù)類型不同。
    2. DataSet的每一行都是case class,在自定義case class之后可以很方便的獲取每一行的信息

五、spark-shell

Spark的shell作為一個強大的交互式數(shù)據(jù)分析工具,提供了一個簡單的方式學習API。它可以使用Scala(在Java虛擬機上運行現(xiàn)有的Java庫的一個很好方式)或Python。spark-shell的本質是在后臺調用了spark-submit腳本來啟動應用程序的,在spark-shell中會創(chuàng)建了一個名為sc的SparkContext對象。

\color{red}{【注】spark-shell只能以client方式啟動。}

查看幫助

$ spark-shell --help
25.png

spark-shell常用選項

--master MASTER_URL 指定模式(spark://host:port, mesos://host:port, yarn,
                              k8s://https://host:port, or local (Default: local[*]))
--executor-memory MEM 指定每個Executor的內存,默認1GB
--total-executor-cores NUM 指定所有Executor所占的核數(shù)
--num-executors NUM 指定Executor的個數(shù)
--help, -h 顯示幫助信息
--version 顯示版本號

從上面幫助看,spark有五種運行模式:spark、mesos、yarn、k8s、local。這里主要講local和yarn模式

Master URL 含義
local 在本地運行,只有一個工作進程,無并行計算能力
local[K] 在本地運行,有 K 個工作進程,通常設置 K 為機器的CPU 核心數(shù)量
local[*] 在本地運行,工作進程數(shù)量等于機器的 CPU 核心數(shù)量。
spark://HOST:PORT 以 Standalone 模式運行,這是 Spark 自身提供的集群運行模式,默認端口號: 7077
mesos://HOST:PORT 在 Mesos 集群上運行,Driver 進程和 Worker 進程運行在 Mesos 集群上,部署模式必須使用固定值:--deploy-mode cluster
yarn 在yarn集群上運行,依賴于hadoop集群,yarn資源調度框架,將應用提交給yarn,在ApplactionMaster(相當于Stand alone模式中的Master)中運行driver,在集群上調度資源,開啟excutor執(zhí)行任務。
k8s 在k8s集群上運行

1)local

在Spark Shell中,有一個專有的SparkContext已經為您創(chuàng)建好了,變量名叫做sc。自己創(chuàng)建的SparkContext將無法工作。可以用--master參數(shù)來設置SparkContext要連接的集群,用--jars來設置需要添加到CLASSPATH的jar包,如果有多個jar包,可以使用逗號分隔符連接它們。例如,在一個擁有2核的環(huán)境上運行spark-shell,使用:

#資源存儲的位置,默認為本地,以及使用什么調度框架 ,默認使用的是spark內置的資源管理和調度框架Standalone 
# local單機版,只占用一個線程,local[*]占用當前所有線程,local[2]:2個CPU核運行
$ spark-shell --master local[2]
# --master 默認為 local[*] 
#默認使用集群最大的內存大小
--executor-memorty
#默認使用最大核數(shù)
--total-executor-cores 
$ spark-shell --master local[*] --executor-memory 1g --total-executor-cores 1
26.png

Web UI地址:http://hadoop-node1:4040

27.png

隨后,就可以使用spark-shell內使用Scala語言完成一定的操作。這里做幾個簡單的操作,有興趣的話,可以自行去了解scala

val textFile = sc.textFile("file:///opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/README.md")
textFile.count()
textFile.first()
28.png

其中,count代表RDD中的總數(shù)據(jù)條數(shù);first代表RDD中的第一行數(shù)據(jù)。

2)on Yarn(推薦)

# on yarn,也可以在配置文件中修改這個字段spark.master
$ spark-shell --master yarn 

--master用來設置context將要連接并使用的資源主節(jié)點,master的值是standalone模式中spark的集群地址、yarn或mesos集群的URL,或是一個local地址。

六、SparkSQL和Hive的集成(Spark on Hive)

1)創(chuàng)建軟鏈接

$ ln -s /opt/bigdata/hadoop/server/apache-hive-3.1.2-bin/conf/hive-site.xml /opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/conf/hive-site.xml

2)復制 hive lib目錄 下的mysql連接jar包到spark的jars下

$ cp /opt/bigdata/hadoop/server/apache-hive-3.1.2-bin/lib/mysql-connector-java-5.1.49-bin.jar /opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/jars/

3)配置

# 創(chuàng)建spark日志在hdfs存儲目錄
$ hadoop fs -mkdir -p /tmp/spark
$ cd /opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/conf
$ cp spark-defaults.conf.template spark-defaults.conf

在spark-defaults.conf追加如下配置:

# 使用yarn模式
spark.master                     yarn
spark.eventLog.enabled           true
spark.eventLog.dir               hdfs://hadoop-node1:8082/tmp/spark
spark.serializer                 org.apache.spark.serializer.KryoSerializer
spark.driver.memory              512m
spark.executor.extraJavaOptions  -XX:+PrintGCDetails -Dkey=value -Dnumbers="one two three"

4)啟動 spark-shell操作Hive(local)

支持多用戶得啟動metastore服務

$ nohup hive --service metastore &
$ ss -atnlp|grep 9083

在hive-site.xml加入如下配置:

<property>  
  <name>hive.metastore.uris</name>  
  <value>thrift://hadoop-node1:9083</value>  
</property>  

啟動spark-sql

# yarn模式,--master yarn可以不帶,因為上面在配置文件里已經配置了yarn模式了
$ spark-sql --master yarn
show databases;
29.png

從上圖就可發(fā)現(xiàn),已經查到我之前創(chuàng)建的庫了,說明已經集成ok了。

七、Spark beeline

Spark Thrift Server 是 Spark 社區(qū)基于 HiveServer2 實現(xiàn)的一個 Thrift 服務。旨在無縫兼容
HiveServer2。因為 Spark Thrift Server 的接口和協(xié)議都和 HiveServer2 完全一致,因此我們部署好Spark Thrift Server后,可以直接使用hive的beeline訪問Spark Thrift Server執(zhí)行相關語句。Spark Thrift Server 的目的也只是取代 HiveServer2,因此它依舊可以和 Hive Metastore進行交互,獲取到 hive 的元數(shù)據(jù)。

1)Spark Thrift Server架構于HiveServer2架構對比

30.png

2)Spark Thrift Server和HiveServer2的區(qū)別

Hive on Spark Spark Thrift Server
任務提交模式 每個session都會創(chuàng)建一個RemoteDriver,也就是對于一個Application。之后將sql解析成執(zhí)行的物理計劃序列化后發(fā)到RemoteDriver執(zhí)行 本身的Server服務就是一個Driver,直接接收sql執(zhí)行。也就是所有的session都共享一個Application
性能 性能一般 如果存儲格式是orc或者parquet,性能會比hive高幾倍,某些語句甚至會高幾十倍。其他格式的話,性能相差不是很大,有時hive性能會更好
并發(fā) 如果任務執(zhí)行不是異步的,就是在thrift的worker線程中執(zhí)行,受worker線程數(shù)量的限制。異步的話則放到線程池執(zhí)行,并發(fā)度受異步線程池大小限制。 處理任務的模式和Hive一樣。
sql兼容 主要支持ANSI SQL 2003,但并不完全遵守,只是大部分支持。并擴展了很多自己的語法 Spark SQL也有自己的實現(xiàn)標準,因此和hive不會完全兼容。具體哪些語句會不兼容需要測試才能知道
HA 可以通過zk實現(xiàn)HA 沒有內置的HA實現(xiàn),不過spark社區(qū)提了一個issue并帶上了patch,可以拿來用:https://issues.apache.org/jira/browse/SPARK-11100

【總結】Spark Thrift Server說白了就是小小的改動了下HiveServer2,代碼量也不多。雖然接口和HiveServer2完全一致,但是它以單個Application在集群運行的方式還是比較奇葩的。可能官方也是為了實現(xiàn)簡單而沒有再去做更多的優(yōu)化。

3)配置啟動Spark Thrift Server

1、配置hive-site.xml

<!-- hs2端口 -->
<property>
  <name>hive.server2.thrift.port</name>
  <value>11000</value>
</property>

2、啟動spark thriftserver服務(不能起hs2,因為配置是一樣的,會有沖突)

$ cd /opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/sbin
$ ./start-thriftserver.sh
$ ss -tanlp|grep 11000
31.png

3、啟動beeline操作

# 為了和hive的區(qū)別,這里使用絕對路徑啟動
$ cd /opt/bigdata/hadoop/server/spark-3.2.0-bin-hadoop3.2/bin
# 操作跟hive操作一模一樣,只是計算引擎不一樣了,換成了spark了
$ ./beeline
!connect jdbc:hive2://hadoop-node1:11000
show databases;
32.png

訪問HDFS WEB UI:http://hadoop-node1:8088/cluster/apps

33.png

34.png

35.png

八、Spark Streaming

Spark Streaming與其他大數(shù)據(jù)框架Storm、Flink一樣,Spark Streaming是基于Spark Core基礎之上用于處理實時計算業(yè)務的框架。其實現(xiàn)就是把輸入的流數(shù)據(jù)進行按時間切分,切分的數(shù)據(jù)塊用離線批處理的方式進行并行計算處理。原理如下圖:

36.png

支持多種數(shù)據(jù)源獲取數(shù)據(jù):


37.png

Spark處理的是批量的數(shù)據(jù)(離線數(shù)據(jù)),Spark Streaming實際上處理并不是像Strom一樣來一條處理一條數(shù)據(jù),而是將接收到的實時流數(shù)據(jù),按照一定時間間隔,對數(shù)據(jù)進行拆分,交給Spark Engine引擎,最終得到一批批的結果。

38.png

由于考慮到本篇文章篇幅太長,所以這里只是稍微提了一下,如果有時間會繼續(xù)補充Spark Streaming相關的知識點,請耐心等待……

官方文檔:https://spark.apache.org/docs/3.2.0/streaming-programming-guide.html

39.png

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容