Spark Streaming的简介和原理
Spark Streaming接收Kafka、Flume、HDFS等各种数据源的实时输入数据,进行处理后,将数据保存在HDFS、Database等地方。
Spark处理的是批量的数据(离线数据),Spark Streamings也是基于核心Spark的,Spark Streaming在内部的处理机制,接收实时的输入数据流,并根据一定的时间间隔拆分成一批批的数据,然后通过Spark Engine处理这些批数据,最终得到处理后的一批批结果数据。
Spark Streaming支持一个高度的抽象,叫做离散流或者DStream,它代表连续的数据流。
DStream既可以根据Kafka、Flume等数据源来获取输入数据流来创建,也可以在DStream的基础上通过高阶函数来获得。
在内部,DStream是由一系列的RDD组成的,一批数据在spark内核中对应一个RDD实例。因此,对应流的DStream可以看成一组RDD,即RDD的一个序列。
Spark Streaming的一些常用数据说明:
名称 | 说明 |
---|---|
离散流 | Spark Streaming对内部连续的实时数据流的抽象描述 |
时间片 | 拆分流数据的时间单元 |
批数据 | 一个时间片内所包含的流数据,表示一个RDD |
窗口(Window) | 一个时间段,系统支持对一个窗口内的数据进行计算 |
窗口长度 | 一个窗口所覆盖的流数据的时间长度,必须是批处理时间间隔的倍数 |
滑动步长 | 前一个窗口到后一个窗口所经过的时间长度,必须是批处理时间间隔的倍数 |
transform(黑名单过滤)
spark Streaming的算子几乎都是作用在DStream中,而DStream的本质是RDD,如果想要直接操作DStream中的RDD,需要借助算子transfrom
nc -lk ip port
数据:
197.129.76.123##2020-10-19 11:11:11##GET /taobao/image/common/1.jpg Http/1.1##200##1271
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
object Demo_Transform extends LoggerTrait {
def main(args: Array[String]): Unit = {
val ssc: StreamingContext = SparkUtils.getStreamingContext("Demo_Transform")
//黑名单
val blackRDD: RDD[(String, Boolean)] = ssc.sparkContext.parallelize(
List(
("127.129.76.113", true)
)
)
val socket: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.10.111", 9999)
val mapDStream: DStream[(String, String)] = socket.map(
line => {
val index: Int = line.indexOf("##")
val ip: String = line.substring(0, index)
val other: String = line.substring(index + 2)
(ip, other)
}
)
val resDStream: DStream[(String, Option[Boolean])] = mapDStream.transform(
rdd => {
val joinRDD: RDD[(String, (String, Option[Boolean]))] = rdd.leftOuterJoin(blackRDD)
joinRDD.filter {
case (ip, (left, right)) => !right.isDefined
}.map {
case (ip, (left, right)) => (ip, right)
}
}
)
resDStream.print()
ssc.start()
ssc.awaitTermination()
}
}
updateStatusByKey(WordCount)
注意点:设置检查点,用于保存上一次运行的结果;
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
/**
* 根据key的前置状态和key的新值进行key的更新,统计截止到目前位置的key的状态
*/
object Demo_updateStateByKey extends LoggerTrait {
def main(args: Array[String]): Unit = {
val ssc: StreamingContext = SparkUtils.getStreamingContext("Demo_updateStateByKey")
ssc.checkpoint("file:/c:/data")
val scokDStream: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.10.111", 9999)
val mapDStream: DStream[(String, Int)] = scokDStream.flatMap(_.split("\\s+")).map((_, 1))
val resDStrean: DStream[(String, Int)] = mapDStream.updateStateByKey(updateFunc)
resDStrean.print()
ssc.start()
ssc.awaitTermination()
}
def updateFunc(seq:Seq[Int], option:Option[Int])={
Option(seq.sum+option.getOrElse(0))
}
}
SparkSql和SparkSession的聚合
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
object Demo_Top3 extends LoggerTrait {
def main(args: Array[String]): Unit = {
//统计全局变量
val spark: SparkSession = SparkUtils.getSparkSession("Demo_Top3")
val ssc = new StreamingContext(spark.sparkContext, Seconds(2))
import spark.implicits._
ssc.checkpoint("file:/c:/data")
val sockDStream: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.10.111", 9999)
val filterDStream: DStream[(String, Int)] = sockDStream.map(line => {
val fields: Array[String] = line.split("\\s+")
if (fields == null || fields.length != 3) {
("", -1)
} else {
val brand = fields(1)
val category = fields(2)
(s"${category}_${brand}", 1)
}
}).filter(kv => kv._2 != -1)
val usbDStream: DStream[(String, Int)] = filterDStream.updateStateByKey(updateFunc)
//统计tops
usbDStream.foreachRDD((rdd, time)=>{
if (!rdd.isEmpty()){
val df: DataFrame = rdd.map {
case (cb, cnt) => {
val category: String = cb.substring(0, cb.indexOf("_"))
val brand: String = cb.substring(cb.indexOf("_") + 1)
(category, brand, cnt)
}
}.toDF("a1", "a2", "a3")
df.createOrReplaceTempView("Tmp")
val sql =
"""
|select
|A.a1,
|A.a2,
|A.a3,
|A.rank
|from(
|select a1,
|a2,
|a3,
|row_number() over(partition by a1 order by cnt desc) rank
|from
|tmp)A
|where A.rank < 4
|""".stripMargin
spark.sql(sql).show()
}
})
ssc.start()
ssc.awaitTermination()
}
def updateFunc(seq:Seq[Int], option:Option[Int])={
Option(seq.sum+option.getOrElse(0))
}
}
SparkStreaming和Kafka的整合
准备工作:
1,启动Zookeeper和Kafka
2,创建主题
3,利用Kafka生产者生产数据
一、receiver方式
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.kafka.KafkaUtils
object Demo_Kafka_Receiver extends LoggerTrait {
def main(args: Array[String]): Unit = {
//1,获取到Streaming的入口
val ssc: StreamingContext = SparkUtils.getStreamingContext("Demo_Kafka_Receiver")
//2,spark-streaming-kafka依赖包提供了一个工具类,帮助我们使用spark-streaming来获取到kafka中的数据
val zkQuorum:String = "192.168.10.111/kafka"
val groupId = "hzbigdata2004"
val topics = Map(
"receiver_topics" -> 1
)
val msgDStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
//打印
msgDStream.print()
//启动
ssc.start()
//始终处于阻塞状态
ssc.awaitTermination()
}
}
恰好一次最好的方法是保存数据处理和偏移量保存的原子性,这就必须使用spark streaming的预写日志机制(Write Ahead Log,WAL)。这种方式虽然能够解决问题,但是性能较差,所以receiver模式被抛弃了。
二、Direct模式
最大的好处就是可以手动的保存偏移量,实现恰好一次
import kafka.serializer.StringDecoder
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka.KafkaUtils
object Demo_Direct {
def main(args: Array[String]): Unit = {
//获取入口
val ssc: StreamingContext = SparkUtils.getStreamingContext("Demo_Direct")
val topic: Set[String] = "direct_topics".split(",").toSet
val kafkaParams:Map[String, String] = Map(
"bootstrap.servers"->"192.168.10.111:9092",
"group.id" -> "hzbigdata2004",
"auto.offset.reset"->"largest"
)
val msgDStream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topic)
//获取到离散流
}
}