大数据技术之_19_Spark学习_04
第1章 Spark Streaming 概述
1.1 什么是 Spark Streaming
[外链图片转存失败(img-iWhfZZpV-1562670398710)(https://s2.ax1x.com/2019/04/29/E3MUsK.png)]
Spark Streaming 类似于 Apache Storm,用于流式数据的处理。根据其官方文档介绍,Spark Streaming 有高吞吐量和容错能力强等特点。Spark Streaming 支持的数据输入源很多,例如:Kafka、Flume、Twitter、ZeroMQ 和简单的 TCP 套接字等等。数据输入后可以用 Spark 的高度抽象,如:map、reduce、join、window 等进行运算。而结果也能保存在很多地方,如 HDFS,数据库等。另外 Spark Streaming 也能和 MLlib(机器学习)以及 Graphx 完美融合。
[外链图片转存失败(img-pXRnayIa-1562670398711)(https://s2.ax1x.com/2019/04/29/E3MaqO.png)]
和 Spark 基于 RDD 的概念很相似,Spark Streaming 使用离散化流(discretized stream)作为抽象表示,叫作 DStream。DStream 是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为 RDD 存在,而 DStream 是由这些 RDD 所组成的序列(因此得名“离散化”)。
[外链图片转存失败(img-NJpsN86Q-1562670398711)(https://s2.ax1x.com/2019/04/29/E3MYxx.png)]
DStream 可以从各种输入源创建,比如 Flume、Kafka 或者 HDFS。创建出来的 DStream 支持两种操作,一种是转化操作(transformation),会生成一个新的 DStream,另一种是输出操作(output operation),可以把数据写入外部系统中。DStream 提供了许多与 RDD 所支持的操作相类似的操作支持,还增加了与时间相关的新操作,比如滑动窗口。
Spark Streaming 的关键抽象
[外链图片转存失败(img-flcCjl9Q-1562670398712)(https://s2.ax1x.com/2019/04/29/E3MJR1.png)]
DStream:Discretized Stream 离散化流
[外链图片转存失败(img-wbrCq9Xa-1562670398712)(https://s2.ax1x.com/2019/04/29/E3MGGR.png)]
1.2 为什么要学习 Spark Streaming
1、易用
2、容错
3、易整合到 Spark 体系
[外链图片转存失败(img-wXBH47a0-1562670398712)(https://s2.ax1x.com/2019/04/29/E3MNM6.png)]
1.3 Spark 与 Storm 的对比
[外链图片转存失败(img-3b1XuWp8-1562670398712)(https://s2.ax1x.com/2019/04/29/E3MwZD.png)]
第2章 运行 Spark Streaming
[外链图片转存失败(img-c9hg0NbJ-1562670398712)(https://s2.ax1x.com/2019/04/29/E3MyRI.png)]
通过 IDEA 编写程序
pom.xml 加入以下依赖:
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>${spark.version}</version>
<!-- provided 表示编译期可用,运行期不可用 -->
<!--<scope>provided</scope>-->
</dependency>
示例代码如下:
package com.atguigu.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{
Seconds, StreamingContext}
object WorldCount {
def main(args: Array[String]) {
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(conf, Seconds(1))
// Create a DStream that will connect to hostname:port, like localhost:9999
val lines = ssc.socketTextStream("hadoop102", 9999)
// Split each line into words
val words = lines.flatMap(_.split(" "))
// import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3
// Count each word in each batch
val pairs = words.map(word => (word, 1))
val results = pairs.reduceByKey(_ + _)
// Print the first ten elements of each RDD generated in this DStream to the console
results.print()
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
ssc.stop()
}
}
安装 Netcat 后,参考文章链接:https://www.cnblogs.com/chenmingjun/p/10785438.html
先启动 Netcat,然后通过 Netcat 发送数据:
$ nc -l -p 9999 #监听9999端口
hello world #运行 jar 包后,发送测试数据
再按照 Spark Core 中的方式进行打包,并将程序上传到Spark机器上。并运行:
/opt/module/spark-2.1.1-bin-hadoop2.7/bin/spark-submit --class com.atguigu.streaming.WorldCount /opt/software/sparkjars/networdcount-jar-with-dependencies.jar
注意
:如果程序运行时,log 日志太多,可以将 spark 的 conf 目录下的 log4j 文件里面的日志级别改成 WARN。
第3章 架构与抽象
Spark Streaming 使用“微批次”的架构,把流式计算当作一系列连续的小规模批处理来对待。Spark Streaming 从各种输入源中读取数据,并把数据分组为小的批次。新的批次按均匀的时间间隔创建出来。在每个时间区间开始的时候,一个新的批次就创建出来,在该区间内收到的数据都会被添加到这个批次中。在时间区间结束时,批次停止增长。时间区间的大小是由批次间隔这个参数决定的。批次间隔一般设在 500 毫秒到几秒之间,由应用开发者配置。每个输入批次都形成一个 RDD,以 Spark 作业的方式处理并生成其他的 RDD。处理的结果可以以批处理的方式传给外部系统。高层次的架构如下图所示:
[外链图片转存失败(img-VNjcqzKJ-1562670398712)(https://s2.ax1x.com/2019/04/29/E3MgQP.png)]
Spark Streaming 的编程抽象是离散化流,也就是 DStream。它是一个 RDD 序列,每个 RDD 代表数据流中一个时间片内的数据。
[外链图片转存失败(img-mR9tG9JF-1562670398713)(https://s2.ax1x.com/2019/04/29/E3M0de.png)]
Spark Streaming 在 Spark 的驱动器程序 – 工作节点的结构的执行过程如下图所示。Spark Streaming 为每个输入源启动对应的接收器。接收器以任务的形式运行在应用的执行器进程中,从输入源收集数据并保存为 RDD。它们收集到输入数据后会把数据复制到另一个执行器进程来保障容错性(默认行为)。数据保存在执行器进程的内存中,和缓存 RDD 的方式一样。驱动器程序中的 StreamingContext 会周期性地运行 Spark 作业来处理这些数据,把数据与之前时间区间中的 RDD 进行整合。
[外链图片转存失败(img-iTwlnhlj-1562670398714)(https://s2.ax1x.com/2019/04/29/E3MBIH.png)]
第4章 Spark Streaming 解析
4.1 初始化 StreamingContext
源码:
import org.apache.spark._
import org.apache.spark.streaming._
// 可以通过 ssc.sparkContext 来访问 SparkContext
val conf = new SparkConf().setAppName(appName).setMaster(master)
val ssc = new StreamingContext(conf, Seconds(1))
// 或者通过已经存在的 SparkContext 来创建 StreamingContext
import org.apache.spark.streaming._
val sc = ... // existing SparkContext
val ssc = new StreamingContext(sc, Seconds(1))
初始化完 Context 之后:
1)定义消息输入源来创建 DStreams。
2)定义 DStreams 的转化操作和输出操作。
3)通过 streamingContext.start() 来启动消息采集和处理.
4)等待程序终止,可以通过 streamingContext.awaitTermination() 来设置。
5)通过 streamingContext.stop() 来手动终止处理程序。
注意:
StreamingContext 一旦启动,对 DStreams 的操作就不能修改了。
在同一时间一个 JVM 中只有一个 StreamingContext 可以启动。
stop() 方法将同时停止 SparkContext,可以传入参数 stopSparkContext 用于只停止 StreamingContext。
4.2 什么是 DStreams
Discretized Stream 是 Spark Streaming 的基础抽象,代表持续性的数据流和经过各种 Spark 原语操作后的结果数据流。在内部实现上,DStream 是一系列连续的 RDD 来表示。每个 RDD 含有一段时间间隔内的数据,如下图:
[外链图片转存失败(img-3Tuu5LVZ-1562670398715)(https://s2.ax1x.com/2019/04/29/E3MsJA.png)]
对数据的操作也是按照 RDD 为单位来进行的,如下图:
[外链图片转存失败(img-KnCoYdq0-1562670398715)(https://s2.ax1x.com/2019/04/29/E3Mrid.png)]
计算过程由 Spark Engine 来完成,如下图:
[外链图片转存失败(img-KB6dXS6P-1562670398715)(https://s2.ax1x.com/2019/04/29/E3MfeS.png)]
4.3 DStream 的输入
Spark Streaming 原生支持一些不同的数据源。一些“核心”数据源已经被打包到 Spark Streaming 的 Maven 工件中,而其他的一些则可以通过 spark-streaming-kafka 等附加工件获取。每个接收器都以 Spark 执行器程序中一个长期运行的任务的形式运行,因此会占据分配给应用的 CPU 核心。此外,我们还需要有可用的 CPU 核心来处理数据。这意味着如果要运行多个接收器,就必须至少有和接收器数目相同的核心数,还要加上用来完成计算所需要的核心数。例如,如果我们想要在流计算应用中运行 10 个接收器,那么至少需要为应用分配 11 个 CPU 核心。所以如果在本地模式运行,不要使用 local 或者 local[1]。
4.3.1 基本数据源
文件数据源(实际开发中这种方式用的比较少)
Socket 数据流前面的例子已经看到过。
文件数据流:能够读取所有 HDFS API 兼容的文件系统文件,通过 fileStream 方法进行读取。
streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)
Spark Streaming 将会监控 dataDirectory 目录并不断处理移动进来的文件,注意:目前不支持嵌套目录。
1)文件需要有相同的数据格式。
2)文件进入 dataDirectory 的方式需要通过移动或者重命名来实现。
3)一旦文件移动进目录,则不能再修改,即便修改了也不会读取新的数据。
如果文件比较简单,则可以使用 streamingContext.textFileStream(dataDirectory)
方法来读取文件。文件流不需要接收器,不需要单独分配 CPU 核。
Hdfs 读取实例:(需要提前在 HDFS 上建好目录
)
scala> import org.apache.spark.streaming._
import org.apache.spark.streaming._
scala> val ssc = new StreamingContext(sc, Seconds(1))
ssc: org.apache.spark.streaming.StreamingContext = org.apache.spark.streaming.StreamingContext@4eb3b690
scala> val lines = ssc.textFileStream("hdfs://hadoop102:9000/data/")
lines: org.apache.spark.streaming.dstream.DStream[String] = org.apache.spark.streaming.dstream.MappedDStream@14c7ab73
scala> val words = lines.flatMap(_.split(" "))
words: org.apache.spark.streaming.dstream.DStream[String] = org.apache.spark.streaming.dstream.FlatMappedDStream@125bc00d
scala> val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
wordCounts: org.apache.spark.streaming.dstream.DStream[(String, Int)] = org.apache.spark.streaming.dstream.ShuffledDStream@4a3363c9
scala> wordCounts.print()
scala> ssc.start()
上传文件到 HDFS 进行测试:
[atguigu@hadoop102 hadoop-2.7.2]$ bin/hdfs dfs -mkdir /data/
[atguigu@hadoop102 hadoop-2.7.2]$ ls
bin data etc include input lib libexec LICENSE.txt logs NOTICE.txt README.txt safemode.sh sbin share wcinput wcoutput
[atguigu@hadoop102 hadoop-2.7.2]$ bin/hdfs dfs -put ./LICENSE.txt /data/
[atguigu@hadoop102 hadoop-2.7.2]$ bin/hdfs dfs -put ./README.txt /data/
获取计算结果:
-------------------------------------------
Time: 1504665716000 ms
-------------------------------------------
-------------------------------------------
Time: 1504665717000 ms
-------------------------------------------
-------------------------------------------
Time: 1504665718000 ms
-------------------------------------------
(227.7202-1,2)
(created,2)
(offer,8)
(BUSINESS,11)
(agree,10)
(hereunder,,1)
(“control”,1)
(Grant,2)
(2.2.,2)
(include,11)
...
-------------------------------------------
Time: 1504665740000 ms
-------------------------------------------
(under,1)
(Technology,1)
(distribution,2)
(http://hadoop.apache.org/core/,1)
(Unrestricted,1)
(740.13),1)
(check,1)
(have,1)
(policies,1)
(uses,1)
...
-------------------------------------------
Time: 1504665741000 ms
-------------------------------------------
自定义数据源(实际开发中用的较多)
通过继承 Receiver,并实现 onStart、onStop 方法来自定义数据源采集。
// Receiver 需要提供一个类型参数,该类型参数是 Receiver 接收到的数据的类型
class CustomReceiver(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) {
override def onStart(): Unit = {
// Start the thread that receives data over a connection
new Thread("Socket Receiver") {
// 定义一个新的线程去执行 receive() 方法
override def run() {
receive()
}
}.start()
}
override def onStop(): Unit = {
// There is nothing much to do as the thread calling receive()
// is designed to stop by itself if isStopped() returns false
}
/**
* Create a socket connection and receive data until receiver is stopped
*/
private def receive() {
var socket: Socket = null
var userInput: String = null
try {
// Connect to host:port
socket = new Socket(host, port)
// Until stopped or connection broken continue reading
// 获取 Socket 的输入对象
val reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
userInput = reader.readLine()
// 当 Receiver 没有停止并且 userInput 不为空
while (!isStopped && userInput != null) {
// 通过 store() 方法将获取到的 userInput 提交给 Spark 框架
store(userInput)
// 再获取下一条
userInput = reader.readLine()
}
reader.close()
socket.close()
// Restart in an attempt to connect again when server is active again
restart("Trying to connect again")
} catch {
case e: java.net.ConnectException =>
// restart if could not connect to server
restart("Error connecting to " + host + ":" + port, e)
case t: Throwable =>
// restart if there is any other error
<