一、流式计算的概述
1.1 什么是流式计算
1. 数据流与静态数据的区别
-- 数据流指的就是不断产生的数据,是源源不断,不会停止。
-- 静态数据指的就是存储在磁盘中的固定的数据
2. 流式计算的概念
就是对数据流进行计算,由于数据是炼苗不断的产生的,所以这个计算也是一直再计算,不会停止。
3. 流式计算的数据流有什么特点:
- 数据是无界的(unbounded)
- 数据是动态的
- 计算速度是非常快的(是不断计算的,每次计算都是微小的批量数据,因此速度快,而且还是基于内存的)
- 计算不止一次
- 计算不能终止
4. 离线计算的特点:
- 数据是有界的(unbounded)
- 数据是静态的
- 计算速度通常较慢
- 计算只执行一次
- 计算终会终止
1.2 常见的离线和流式计算框架
1. 离线计算框架
-- mapreduce
-- hive
-- sparkcore
-- sparksql
-- flink-dataset
2. 流式计算框架
-- storm
-- sparkStreaming
-- flink-datastream(blink)
1.3 SparkStreaming简介
1.3.1 简介
1. SparkStreaming也是Spark生态栈中的一个重要模块,是一个流式计算框架
2. SparkStreaming属于准实时计算框架
3. SparkStreaming是SparkCore的api的一种扩展,使用DStream(离散流)作为数据模型。 本质就是一个时间序列上的RDD。
DStream,本质上是RDD的序列。SparkStreaming的处理流程可以归纳为下图:
流式计算框架从延迟的角度来分类:
1. 纯实时流式计算: 毫秒级别的延迟,或者没有延迟的计算。
2. 准实时流式计算: 亚秒级别,秒级别,分钟级别的计算
流式计算框架从处理的记录条数来分类
1. 纯实时流式计算: 来一条记录,就计算一条记录。
2. 准实时流式计算: 微小的批处理,还是多条记录一起计算。
1.3.2 原理
DStream数据流模型
1. SparkStreaming 会实时的接受输入的数据
2. SparkStreaming 会按照固定长度的时间段将源源不断进来的数据划分成batch
3. SparkStreming 会每一个batch进行一次计算,计算是不停止的
4. 每次的计算结果也是一个batch,因此结果集就是多个batch的构成
5. SparkStreaming,将数据流抽象成DStream. 称之为离散流的数据模型。本质就是一个时间序列上的RDD。
6. 在整个数据流作业中,会有多个DStream。
参考下图: rdd1 就是一个时间序列上的 DStream
rdd2 就是一个时间序列上的 DStream
rdd3 就是一个时间序列上的 DStream
rdd4 就是一个时间序列上的 DStream
8:00:00 hello world hello java hello c++
rdd1 = sc.textFile("....")
rdd2 = rdd1.flatMap(_.split(" "))
rdd3 = rdd2.map((_,1))
rdd4 = rdd3.reduceByKey(_+_)
针对于rdd1来说:
8:00:00 hello world hello java hello c++
8:00:10 no zuo no die
8:00:20 you are best
8:00:30: hello you are best
针对于rdd2来说:
8:00:00 [hello, world,hello,java,hello,c++]
8:00:10 [no,zuo,no,die]
8:00:20 [you,are,best]
8:00:30: [hello,you,are,best]
针对于rdd3来说:
8:00:00 (hello,1), (world,1),(hello,1),(java,1),(hello,1),(c++,1)
8:00:10 (no,1),(zuo,1),(no,1),(die,1)
8:00:20 (you,1),(are,1),(best,1)
8:00:30: (hello,1),(you,1),(are,1),(best,1)
参考下图: 一个DStream是由不同时间段上的同一个RDD构成的
参考下图:如果算子的返回值是DStream,则不管是哪一个时间段上的数据,只要调用了同一个算子,则返回的都同一个DStream
1.3.3 Storm VS
SparkStreaming VS
Flink
1.4 怎样选择流式处理框架
何时选择storm
--需要纯实时,不能忍受1秒以上延迟的场景
--实时计算的功能中,要求可靠的事务机制和可靠性机制,即数据的处理完全精准,一条也不能多,一条也不能少
--针对高峰低峰时间段,动态调整实时计算程序的并行度,以最大限度利用集群资源(通常是在小型公司,集群资源紧张的情况)
何时选择Spark Streaming
--不满足上述3点要求的话,我们可以考虑使用Spark Streaming来进行实时计算
--如果一个项目除了实时计算之外,还包括了离线批处理、交互式查询、图计算和MLIB机器学习等业务功能,而且实时计算中,
可能还会牵扯到高延迟批处理、交互式查询等功能,,那么就应该首选Spark生态,用Spark Core开发离线批处理,
用Spark SQL开发交互式查询,用Spark Streaming开发实时计算,三者可以无缝整合,给系统提供非常高的可扩展性。
何时选择Flink
支持高吞吐、低延迟、高性能的流处理
支持带有事件时间的窗口(Window)操作
支持有状态计算的Exactly-once语义
支持高度灵活的窗口(Window)操作,支持基于time、count、session,以及data-driven的窗口操作
支持具有Backpressure功能的持续流模型
支持基于轻量级分布式快照(Snapshot)实现的容错
一个运行时同时支持Batch on Streaming处理和Streaming处理
Flink在JVM内部实现了自己的内存管理
支持迭代计算
支持程序自动优化:避免特定情况下Shuffle、排序等昂贵操作,中间结果有必要进行缓存
二、SparkStreaming的入门编程
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>redis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- sparkstreaming的核心包 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.2.3</version>
</dependency>
<!-- sparkstreaming与kafka的整合包 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.2.3</version>
</dependency>
<!-- redis的整合包 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
<!-- sparksql的核心包 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
</project>
2.1 wordcount案例演示
package com.qf.sparkstreaming.day01
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{
DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{
Seconds, StreamingContext}
/**
* sparkCore的入门api: SparkContext
* sparkSql的入门: SparkSession
* sparkStreaming的入门API: StreamingContext
*
*
* 注意:
* 1. 要先使用nc指令 开启qianfeng01和10086端口,否则sparkStreaming会提前报错
* 在qianfeng01上运行指令: nc -lp 10086
* -l 表示监听
* -p 表示端口
*/
object Streaming_01_WordCount {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("wordcount")
/**
* 构造器:StreamingContext(conf:SparkConf, batchDuration:Duration)
* 第一个参数:配置对象
* 第二个参数:用于指定SparkStreaming的流式计算的batch的时间间隔,即时间片段
* Durations.milliseconds(milliseconds: Long) 毫秒级别
* Durations.seconds(seconds: Long) 秒级别
* Durations.minutes(minutes: Long) 分钟级别
* Milliseconds(milliseconds: Long) 毫秒级别
* Seconds(seconds: Long) 秒级别
* Minutes(minutes: Long) 分钟级别
*/
val context = new StreamingContext(conf, Seconds(10))
/**
* 利用TCP协议的套接字,实时的监听一个端口,如果有数据,就采集,并计算。
* socketTextStream(hostname: String,port: Int,......)
* T: 泛型
* hostname: 要监听的主机名
* port:要监听的端口号
*
*/
val dStream: ReceiverInputDStream[String] = context.socketTextStream("qianfeng01", 10086)
// 打印数据流中的数据,默认打印10条记录
//dStream.print()
// 按照空格切分成各个单词, 返回的是一个新的DStream
val wordDStream: DStream[String] = dStream.flatMap(_.split(" "))
//构建成元组,返回一个新的DStream
val wordAndOneDStream: DStream[(String, Int)] = wordDStream.map((_, 1))
//进行统计每个单词的数量,返回的是一个新的DStream
val wordCountDStream: DStream[(String, Int)] = wordAndOneDStream.reduceByKey(_ + _)
//打印,默认打印10条
wordCountDStream.print()
//启动程序
context.start()
/**
* 因为main方法一旦结束,整个程序就结束,因此需要让main方法处于等待状态
*/
context.awaitTermination()
}
}
2.2 从内存中的Queue中获取数据
package com.qf.sparkstreaming.day01
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.{
Durations, StreamingContext}
import scala.collection.mutable
/**
* 从内存中的Queue中获取数据
*/
object Streaming_02_FromQueue {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("FromQueue")
val ssc:StreamingContext = new StreamingContext(conf,Durations.seconds(10))
/**
* queueStream[T: ClassTag]( queue: Queue[RDD[T]],oneAtATime: Boolean = true)
* 从一个RDD队列中获取一个或多个RDD数据,进行处理。
* queue:RDD队列
* oneAtATime: 是否一次处理一个RDD,默认值是true。 false表示队列中有多少,就一次性处理多少。 注意:从队列中获取数据时,队列中就没有该数据了。
*/
val queue = new mutable.Queue[RDD[Int]]()
val dStream: InputDStream[Int] = ssc.queueStream(queue,true)
//直接打印,默认打印10行
dStream.print()
//开启数据流作业
ssc.start()
/**
* 利用main线程,向队列中源源不断的添加RDD。
*/
val rdd: RDD[Int] = ssc.sparkContext.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8))
for(i<- 1 to 300){
queue.enqueue(rdd)//将rdd填入队列中
Thread.sleep(1000)
// println(queue.size) //如果将oneAtATime改为false,则可证明队列中的数据每10秒都会被清空。
}
// 该方法的作用就是阻塞main方法,不让其结束。因为main方法已结束,就会停止数据流作业
ssc.awaitTermination()
}
}
2.3 自定义接收器
package com.qf.sparkstreaming.day01
import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.receiver.Receiver
import org.apache.spark.streaming.{
Durations, StreamingContext}
object Streaming_03_CustomReceiver {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("FromQueue")
val ssc:StreamingContext = new StreamingContext(conf,Durations.seconds(10))
//从采集器中获取DStream
val dStream: ReceiverInputDStream[String] = ssc.receiverStream(new MyReceiver())
dStream.flatMap(_.split(" ")).map((_,1))</