在大数据的各种框架中,hadoop无疑是大数据的主流,但是随着时代发展,hadoop只适用于离线数据的处理,无法应对一些实时数据的处理分析,我们需要一些实时计算框架来分析数据。因此出现了很多流式实时计算框架,比如Storm,Spark Streaming,Samaz等框架,本文主要讲解Spark Streaming的工作原理以及如何使用。
1. SparkStreaming
SparkStreaming是微批处理,每隔一段时间处理一次,每隔一段时间将接收到的数据封装成一个rdd, 再触发一个job处理rdd中的数据
1.1 Spark Streaming介绍
Spark Streaming是Spark生态系统当中一个重要的框架,它建立在Spark Core之上,下面这幅图也可以看出Sparking Streaming在Spark生态系统中地位。
1.2 Spark Streaming特点
- 高可扩展性,可以运行在上百台机器上(Scales to hundreds of nodes)
- 低延迟,可以在秒级别上对数据进行处理(Achieves low latency)
- 高可容错性(Efficiently recover from failures)
- 能够集成并行计算程序,比如Spark Core(Integrates with batch and interactive processing)
1.3 Spark Streaming工作原理
对于Spark Core它的核心就是RDD,对于Spark Streaming来说,它的核心是DStream,DStream类似于RDD,它实质上一系列的RDD的集合,DStream可以按照秒数将数据流进行批量的划分。首先从接收到流数据之后,将其划分为多个batch,然后提交给Spark集群进行计算,最后将结果批量输出到HDFS或者数据库以及前端页面展示等等。可以参考下面这幅图来帮助理解:
2. Demo1WorldCount
从Socket源端监控收集数据运行wordcount的案例
服务器端:nc -lk 8888 用于测试
在Linux端命令行中输入nc -lk 8888指令进行实时输入数据
package com.xiaoming.streaming
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object Demo1WorldCount {
def main(args: Array[String]): Unit = {
/**
* 创建spark环境
*
*/
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("Demo1WorldCount")
val sc: SparkContext = new SparkContext(conf)
/**
* 创建streaming环境
* 指定每隔多久执行一次
*
*/
val ssc: StreamingContext = new StreamingContext(sc,Durations.seconds(5))
/**
* 读取数据
* 读取socket 数据
* nc -lk 8888 用于测试
*
*/
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master",8888)
/**
* 处理数据
*
*/
val countDS: DStream[(String, Int)] = linesDS
.flatMap(_.split(","))
.map((_, 1))
.reduceByKey(_ + _)
countDS.print()
/**
* 启动spark streaming
*
*/
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
Spark Streaming的程序编写分下面几步:
- 定义一个输入流源,比如获取socket端的数据,HDFS,kafka中数据等等
- 定义一系列的处理转换操作,比如上面的flatmap,map,reduce操作等等,Spark Streaming也有类似于SparkCore的transformation操作
- 启动程序收集数据 start()
- 等待程序停止(遇到错误终止或者手动停止awaitTermination())
- 手动终止应用程序 stop()
3. 有状态算子updateStateByKey
3.1 updateStateByKey与reduceByKey
3.2 Demo2UpdateStateByKey
从Socket源端监控累加收集数据运行wordcount的案例
服务器端:nc -lk 8888 用于测试
在Linux端命令行中输入nc -lk 8888指令进行实时输入数据
package com.xiaoming.streaming
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object Demo2UpdateStateByKey {
def main(args: Array[String]): Unit = {
/**
* 创建spark环境
*
*/
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("Demo2UpdateStateByKey")
val sc: SparkContext = new SparkContext(conf)
/**
* 创建streaming环境
*
*/
val ssc: StreamingContext = new StreamingContext(sc, Durations.seconds(5))
ssc.checkpoint("spark/data/checkpoint")
/**
* 读取数据
*
*/
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
val wordsDS: DStream[String] = linesDS.flatMap(_.split(","))
val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))
/**
* 更新函数 :使用当前batch的数据 更新之前的计算结果,返回一个新的结果
*
* seq:当前batch一个单词所有的value
* opt:之前一个单词的计算结果(状态-),
* 使用Option的原因是:如果第一次计算之前没有结果,就返回None
*
*/
val countDS: DStream[(String, Int)] = kvDS.updateStateByKey((seq: Seq[Int], opt: Option[Int]) => {
//计算当前单词的数量
val currCount: Int = seq.sum
// 获取之前的计算结果
val beforeCount: Int = opt.getOrElse(0)
// 最新的单词数量
val newCount: Int = currCount + beforeCount
//返回Option
Option(newCount)
})
countDS.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
4. Dstream-RDD-DataFrame之间的转换
从Socket源端监控累加收集数据运行wordcount的案例
服务器端:nc -lk 8888 用于测试
在Linux端命令行中输入nc -lk 8888指令进行实时输入数据
package com.xiaoming.streaming
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
object Demo3DStreamToRDD {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.appName("Demo3DStreamToRDD")
.config("spark.sql.shuffle.partitions", 1)
.master("local[2]")
.getOrCreate()
import spark.implicits._
import org.apache.spark.sql.functions._
val sc: SparkContext = spark.sparkContext
val ssc: StreamingContext = new StreamingContext(sc,Durations.seconds(5))
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master",8888)
/**
* foreachRDD
* 将DStream转换成rdd
*/
linesDS.foreachRDD(rdd=>{
println("正在执行foreachRDD")
//rdd
rdd
.flatMap(_.split(","))
.map((_,1))
.reduceByKey(_+_)
// .foreach(println)
/**
* rdd转换成DF
*/
val df: DataFrame = rdd.toDF("line")
df.createOrReplaceTempView("lines")
df.select(explode(split($"line",",")) as "word")
.groupBy($"word")
.agg(count($"word") as "c")
.show()
})
/**
* transform : 将DStream转换成rdd , 在transform里面编写rdd的代码,写完之后需要返回一个新的rdd,
*
*/
val kvDS: DStream[(String, Int)] = linesDS.transform(rdd => {
val kvRDD: RDD[(String, Int)] = rdd
.flatMap(_.split(","))
.map((_, 1))
kvRDD
})
kvDS.reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
5. 滑动窗口reduceByKeyAndWindow
从Socket源端监控累加收集数据运行wordcount的案例
服务器端:nc -lk 8888 用于测试
在Linux端命令行中输入nc -lk 8888指令进行实时输入数据
package com.xiaoming.streaming
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
object Demo5Window {
def main(args: Array[String]): Unit = {
/**
* 创建spark 环境
*
*/
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName("wc")
val sc = new SparkContext(conf)
/**
* 创建spark streaming环境
* 指定每隔多久计算一次
*
*/
val ssc = new StreamingContext(sc, Durations.seconds(5))
ssc.checkpoint("spark/data/checkpoint")
val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
val kvDS: DStream[(String, Int)] = linesDS.flatMap(_.split(",")).map((_, 1))
/**
* 统计最近15秒单词的数量,每隔5秒统计一次
* 窗口大小和滑动时间必须是batch时间的整数倍
*
*/
/*val countDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(
(x: Int, y: Int) => x + y, //聚合函数
Durations.seconds(15), //窗口大小
Durations.seconds(5) //滑动时间
)*/
/**
* 如果窗口存在交叉的清空,会重复计算数据
* 所有可以对窗口计算进行优化,
*/
val countDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(
(x: Int, y: Int) => x + y, //聚合函数
(i: Int, j: Int) => i - j, //减去多余数据的函数
Durations.seconds(15), //窗口大小
Durations.seconds(5) //滑动时间
)
countDS
.filter(_._2 != 0) //过滤无效数据
.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}