SparkSreaming
1.0 概述
1.1-1.2 相关概念
离线和实时 批量和流式
1.3 spark streaming是什么
- spark streaming 用于流式数据的处理
- 处理的时候不是单条的处理而是一批一批来处理
- 有采集周期|批处理间隔的概念,决定和作业的频率和数据处理的延迟,同时也影响着数据处理的吞吐量的性能
- input data stream => Spark Streaming => batches of input data => Spark Engine =>batches of processed data
- 好好理解Dstream的概念:一个周期里的也是用Dstream来封装RDD
1.4 Spark Streaming的特点
优点:易用 容错 易整合
缺点:"微量批处理"的架构,延迟会相对高一些
1.5 Spark Streaming架构
- 程序入口 StreamingContext,也是封装了SparkContext,至少需要两个executor
- 单独来一个executor来接收数据 “Zero Copy”
- 不是一次运行就结束了,而是每一个采集周期就处理一次
- 遇到采集快,处理慢的情况怎么办
- Spark1.5版本以前可以限速采集器,1.5以后采用背压机制;就可以不用限速了,不然即使开了backpressure也不会超过限速的.
2.0 DStream入门
2.1 WordCount案例实操
object Spark01_WOrdCount{
def main(args:Array[String]):Unit={
//创建SparkConf配置文件对象
//至少需要2个线程local[2]
val conf = new SparkConf().setMaster("local[*]").setAppName("111")
//创建SparkSteaming的执行入口
val ssc = new StramingContext(conf,Seconds(3))
//读取指定端口号的数据,并且数据是以流的形式,源源不断的过来
val lineDS : ReceiverInputDSteam[String] = ssc.socketTextStream("hadoop102",9999)
//对当前的一行数据进行扁平映射
val faltMapDS = lineDS.flatMap(_.split(" "))
//对数据进行结构的转换
val mapDS=flatMapDS.map((_,1))
//对单词出现的次数 进行计数
val reduceDS = mapDS.reduceByKey(_+_)
reduceDS.print()
//开始采集
ssc.start()
//释放资源 默认情况下不使用 因为我们要实时的不间断的采集数据,所以不能调用Stop方法结束StreamingCont ext
//让采集线程一直执行
ssc.awaitTermination()
}
}
- 最小化安装要再安装一下netcat,使用命令
sudo yum install -y nc
2.2 WordCount解析
DStream是SparkStreaming的基础抽象,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。
2.3 几点注意
- 一旦StreamingContext已经启动,则不能再添加新的Streaming Computation
- 一旦一个StreamingContext已经停止(sc.stop()),那么它也不能再重启
- 在一个JVM内,同一时间只能启动一个StreamingContext
- stop()的方式停止StreamingContext,也会把SparkContext停掉.如果仅仅想停止StreamingContext,则应该这样:stop(false) 一般不会这么去使用
- 一个SparkContext可以重用去创建多个StreamingContext,前提是以前的StreamingContext已经停掉,并且SparkContext没有被停掉
3.0 DStream创建
3.1 RDD队列(了解)
object Spark02_Create_RDDQueue{
def main(args:Array[String]):Unit={
//创建SparkConf配置文件对象
//至少需要2个线程local[2]
val conf = new SparkConf().setMaster("local[*]").setAppName("111")
//创建SparkSteaming的执行入口
val ssc = new StramingContext(conf,Seconds(3))
//创建RDD队列
var queue : mutable.Queue = new mutable.Queue[RDD[Int]]()
//创建离散化流(第二个参数oneAtTime:是否一个周期只处理一个RDD,默认为true)
val queueDS = ssc.queueStream(queue,false)
//对采集到的DS的数据进行操作
queueDS.map((_,1)).reduceByKey(_+_).print
//开启采集数据的线程
ssc.start()
//向队列中放数据
for(i<- 1 to 5){
//通过ssc获取sparkContext,创建RDD,并将创建好的RD放到RDD队列中
queue.enqueue( ssc.sparkContext.makeRDD(1 to 5))
Thread.sleep(2000)
}
ssc.awaitTermination()
}
3.2 自定义数据源
看视频09_自定义数据源
object Spark03_Create_Customer {
def main(args: Array[String]): Unit = {
//1.创建Spark配置文件对象
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("sparkDemo")
//2.创建Streaming程序执行的入口
val ssc: StreamingContext = new StreamingContext(conf,Seconds(3))
//自定义数据源 创建DStream
val lineDS: ReceiverInputDStream[String] = ssc.receiverStream(new MyReceiver("hadoop202",9999))
lineDS
.flatMap(_.split(" "))
.map((_,1))
.reduceByKey(_+_)
.print()
//开始采集
ssc.start()
ssc.awaitTermination()
}
}
class MyReceiver(host: String,port: Int) extends Receiver[String](StorageLevel.MEMORY_ONLY){
private var socket: Socket = _
override def onStart(): Unit = {
try {
socket = new Socket(host, port)
} catch {
case e: ConnectException =>
restart(s"Error connecting to $host:$port", e)
return
}
new Thread("Socket Receiver") {
setDaemon(true)
override def run(): Unit = { receive() }
}.start()
}
def receive(): Unit = {
//读取指定端口的数据
val bf = new BufferedReader(new InputStreamReader(socket.getInputStream,StandardCharsets.UTF_8))
//定义一个变量,存储读取到的一行的数据
var line:String = null
while((line=bf.readLine())!=null){
//调用store方法,将读到的数据进行缓存
store(line)
}
}
override def onStop(): Unit = {
synchronized {
if (socket != null) {
socket.close()
socket = null
}
}
}
}
3.3 Kafka数据源(面试开发重点)
- Spark-streaming-kafka-0-8 从spark2.30开始就过时了,在Spark3.0已经删除了,原因:还是因为接收和计算的数据速度不同,会导致计算数据的节点内存溢出
-现在用的是Spark-streaming-kafka-0-10
3.3.2 Kafka 0-10 Direct模式
//从Kafka中读取数据 ,创建DStream
object Spark04_Create_Kafka {
def main(args: Array[String]): Unit = {
//1.创建SparkConf
val conf: SparkConf = new SparkConf().setAppName("DirectAPI010").setMaster("local[*]")
//2.创建StreamingContext
val ssc = new StreamingContext(conf, Seconds(3))
//构建Kafka参数
val kafkaParmas: Map[String, Object] = Map[String, Object](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "myGroup",
//ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> "org.apache.kafka.common.serialization.StringDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer]
)
//读取Kafka数据,创建Dstream
val kafkaDS: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](Set("bigdata0317"), kafkaParmas)
)
kafkaDS
.map(_.value())
.flatMap(_.split(" "))
.map((_,1))
.reduceByKey(_+_)
.print()
ssc.start()
ssc.awaitTermination()
}
}
看视频11 主要还是了解0-8
kafka常用命令:
<!-- 查询topic -->
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list
<!-- 创建topic -->
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --replication-factor 1 --partitions 3 --topic bigdata0317
<!-- 删除topic -->
bin/Kafka-topics.sh --bootstrap-server hadoop102:9092 --delete --topic bigdata0317
<!-- 查看topic情况 -->
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --describe --topic first
<!-- 生产数据 -->
bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic first
<!-- 消费数据 -->
bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
3.3.3 消费Kafka数据模式总结
- “0-8 ReceiverAPI”:
- 专门的Executor读取数据,速度不统一
- 跨机器传输数据,WAL
- Executor读取数据通过多个线程的方式,想要增加并行度,则需要多个流union
- offset存储在Zookeeper中
- “0-8 DirectAPI”:
- Executor读取数据并计算
- 增加Executor个数来增加消费的并行度
- offset存储(
CheckPoint(getActiveOrCreate方式创建StreamingContext)
手动维护(有事务的存储系统)
获取offset必须在第一个调用的算子中:offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
)
- “0-10 DirectAPI”:
- Executor读取数据并计算
- 增加Executor个数来增加消费的并行度
- offset存储(
_consumer_offsets系统主题中
手动维护(有事务的存储系统)
)
今日重点:理解DStream 看看Spark-Kafka-0-8
4.0 DStream转换
- DStream上的操作与RDD的类似,分为Transformations(转换)和Output Operations(输出)两种。相当于RDD的转换算子和行动算子.
- 此外转换操作中还有一些比较特殊的算子,如:updateStateByKey()、transform()以及各种Window相关的算子。
4.1 无状态转化操作
- 无状态转化操作就是把简单的RDD转化操作应用到每个批次上,也就是转化DStream中的每一个RDD。(不会把上一个RDD的状态(结果)延续到下一个RDD)
4.1.1 Transform
- 对DStream中的RDD应用转换
object Spark01_Transform {
def main(args: Array[String]): Unit = {
//创建SparkConf
val conf: SparkConf = new SparkConf().setAppName("DirectAPI010").setMaster("local[*]")
//创建StreamingContext
val ssc = new StreamingContext(conf, Seconds(3))
//从指定的端口读取数据
val lineDS : ReceiverInputDSteam[String] = ssc.socketTextStream("hadoop102",9999)
//转换成RDD
val resDS=lineDS.transform(
rdd=>{
val sortRDD = rdd
.flatMap(_.split(" "))
.map((_,1))
.reduceByKey(_+_)
.sortByKey()
sortRDD
}
)
resDS.print()
//开始采集数据
ssc.strat()
ssc.awaitTermination()
4.2 有状态转化操作
4.2.1 UpdateStateByKey
object Spark01_UpdateStateByKey {
def main(args: Array[String]): Unit = {
//创建SparkConf
val conf: SparkConf = new SparkConf().setAppName("DirectAPI010").setMaster("local[*]")
//创建StreamingContext
val ssc = new StreamingContext(conf, Seconds(3))
//设置检查点的目录,用于保存上一个周期的状态
//从指定的端口读取数据
val lineDS : ReceiverInputDSteam[String] = ssc.socketTextStream("hadoop102",9999)
val mapDS= lineDS.faltMap(_.split(" ")).map((_,1))
//reduceByKey只能聚合一个周期内的数据,不传递到下一个周期
//UpdateStateByKey可以记录上一个采集周期的状态与当前的采集周期数据进行聚合
val resDS=mapDS.updateStateByKey(
(seq:Seq[Int],state:Option[Int])=>{
Option(state.getOrElse(0)+seq.sum)
}
)
resDS.print()
//开始采集数据
ssc.strat()
ssc.awaitTermination()
4.2.2 Window Operations(窗口操作)
- 有点像是滑窗
- 所有的窗口操作都需要两个参数:窗口时长和滑动步长:都是周期的整数倍
需求:WordCount统计3秒一个批次,滑动窗口6秒
object Spark03_wordCount {
def main(args: Array[String]): Unit = {
//创建SparkConf
val conf: SparkConf = new SparkConf().setAppName("DirectAPI010").setMaster("local[*]")
//创建StreamingContext
val ssc = new StreamingContext(conf, Seconds(3))
//从指定的端口读取数据
val lineDS : ReceiverInputDSteam[String] = ssc.socketTextStream("hadoop102",9999)
resDS.print()
//开始采集数据
ssc.strat()
ssc.awaitTermination()
4.2.3 关于Window的操作还有如下的方法:
要看看,有点不清晰,晚上也要练习
- window(windowLength, slideInterval)
- 基于对源DStream窗化的批次进行计算返回一个新的Dstream
- countByWindow(windowLength, slideInterval)
- 返回一个滑动窗口计数流中的元素个数
- countByValueAndWindow()
- 返回的DStream则包含窗口中每个值的个数
- reduceByWindow(func, windowLength, slideInterval)
- 通过使用自定义函数整合滑动区间流元素来创建一个新的单元素流
- reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks])
- 当在一个(K,V)对的DStream上调用此函数,会返回一个新(K,V)对的DStream,此处通过对滑动窗口中批次数据使用reduce函数来整合每个key的value值
- reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks])
- 这个函数是上述函数的变化版本,每个窗口的reduce值都是通过用前一个窗的reduce值来递增计算。通过reduce进入到滑动窗口数据并”反向reduce”离开窗口的旧数据来实现这个操作。如果把3秒的时间窗口当成一个池塘,池塘每一秒都会有鱼游进或者游出,那么第一个函数表示每由进来一条鱼,就在该类鱼的数量上累加。而第二个函数是,每游出去一条鱼,就将该鱼的总数减去一。
5.0 DStream输出
5.1 常用输出操作
- print() 主要用于开发测试
- saveAsTextFiles(prefix, [suffix])
- saveAsObjectFiles(prefix, [suffix])
- saveAsHadoopFiles(prefix, [suffix])
- foreachRDD(func) 较为常用的输出,功能和transform类似,不过是行动算子
6.0 DStream编程进阶
6.1 累加器和广播变量
- 和RDD中的操作一样
6.2 DataFrame and SQL Operations
- 可以以SQL表格的形式输出
6.3 Caching/Persistence
- 和RDD类似cache和persit
7.0 Spark Streaming项目实战
kafka有问题该怎么办:
1. 删除zookeeper中的kafka/brokers/ids
2. 删除zookeeper中的kafka目录
3. 删除kafka中 meta.proprety中的broker.ids