流(Streaming),在大数据时代为数据流处理,就像水流一样,是数据流;既然是数据流处理,就会想到数据的流入、数据的加工、数据的流出。
日常工作、生活中数据来源很多不同的地方。例如:工业时代的汽车制造、监控设备、工业设备会产生很多源数据;信息时代的电商网站、日志服务器、社交网络、金融交易系统、黑客攻击、垃圾邮件、交通监控等;通信时代的手机、平板、智能设备、物联网等会产生很多实时数据,数据流无处不在。
在大数据时代Spark Streaming能做什么?
平时用户都有网上购物的经历,用户在网站上进行的各种操作通过Spark Streaming流处理技术可以被监控,用户的购买爱好、关注度、交易等可以进行行为分析。在金融领域,通过Spark Streaming流处理技术可以对交易量很大的账号进行监控,防止罪犯洗钱、财产转移、防欺诈等。在网络安全性方面,黑客攻击时有发生,通过Spark Streaming流处理技术可以将某类可疑IP进行监控并结合机器学习训练模型匹配出当前请求是否属于黑客攻击。其他方面,如:垃圾邮件监控过滤、交通监控、网络监控、工业设备监控的背后都是Spark Streaming发挥强大流处理的地方。
大数据时代,数据价值一般怎么定义?
所有没经过流处理的数据都是无效数据或没有价值的数据;数据产生之后立即处理产生的价值是最大的,数据放置越久或越滞后其使用价值越低。以前绝大多数电商网站盈利走的是网络流量(即用户的访问量),如今,电商网站不仅仅需要关注流量、交易量,更重要的是要通过数据流技术让电商网站的各种数据流动起来,通过实时流动的数据及时分析、挖掘出各种有价值的数据;比如:对不同交易量的用户指定用户画像,从而提供不同服务质量;准对用户访问电商网站板块爱好及时推荐相关的信息。
Storm/JStrom:
完全实时流式数据处理平台
来一条数据就处理一条数据,对机器的性能要求比较高
在高并发高数据量的情况下,延迟性比spark streaming的低
SparkStreaming:
准实时/微观操作的流式数据处理平台
Streaming是按照批次进行执行的,一个一个批次进行执行,一个批次的处理的数据就是批次对应时间段收集得到的数据,只有当上一个批次执行完成后,下一个批次才会开始执行
相对于Storm来讲:
数据的延迟性在大数据量的情况下比较高
SparkStreaming VS Hadoop MR:
Spark Streaming是一个准实时流处理框架,而Hadoop MR是一个离线、批处理框架;很显然,在数据的价值性角度,Spark Streaming完胜于Hadoop MR。
SparkStreaming VS Storm:
Spark Streaming是一个准实时流处理框架,处理响应时间一般以分钟为单位,也就是说处理实时数据的延迟时间是秒级别的;Storm是一个实时流处理框架,处理响应是毫秒级的。所以在流框架选型方面要看具体业务场景。需要澄清的是现在很多人认为Spark Streaming流处理运行不稳定、数据丢失、事务性支持不好等等,那是因为很多人不会驾驭Spark Streaming及Spark本身。在Spark Streaming流处理的延迟时间方面,Spark定制版本,会将Spark Streaming的延迟从秒级别推进到100毫秒之内甚至更少。
SparkStreaming优点:
1、提供了丰富的API,企业中能快速实现各种复杂的业务逻辑。
2、流入Spark Streaming的数据流通过和机器学习算法结合,完成机器模拟和图计算。
3、Spark Streaming基于Spark优秀的血统。
SparkStreaming能不能像Storm一样,一条一条处理数据?
Storm处理数据的方式是以条为单位来一条一条处理的,而Spark Streaming基于单位时间处理数据的,SparkStreaming能不能像Storm一样呢?答案是:可以的。
业界一般的做法是Spark Streaming和Kafka搭档即可达到这种效果
Kafka业界认同最主流的分布式消息框架,此框架即符合消息广播模式又符合消息队列模式。
Kafka内部使用的技术:
1、 Cache
2、 Interface
3、 Persistence(默认最大持久化一周)
4、 Zero-Copy技术让Kafka每秒吞吐量几百兆,而且数据只需要加载一次到内核提供其他应用程序使用
外部各种源数据推进(Push)Kafka,然后再通过Spark Streaming抓取(Pull)数据,抓取的数据量可以根据自己的实际情况确定每一秒中要处理多少数据。
Sparkstreaming的处理流程:
-1. 读取数据形成DStream
读取外部数据形成DStream,比如:KAFKA、Flume....
-2. 数据处理
DStream的API进行操作
-3. 结果数据输出
数据保存外部系统
-a. Redis、MongoDB
-b. RDBMs
-c. HBASE、HDFS、Hive
-d. Kafka
streaming应用结构:
[Flume -> ]Kafka -> SparkStreaming/Storm -> Kafka/HBase -> ...
最常用的应用场景:(业务比较简单)
-1. 基本指标的统计
活跃访客的统计
每个小时访客统计
最近三十分钟访客统计
各个省份最近三十分钟访客数量统计.....
-2. 广告点击量统计
-3. 黑名单统计
-4. 对实时数据进行预测(预测模型在程序运行已经构造完成)
程序入口:
StreamingContext:streaming的上下文对象,依赖SparkContext对象
DStream: 核心抽象,可以当做RDD进行操作
==============SparkStreamingWordCount=================
nc -lk 9999 //用于数据的输入
sbin/start-dfs.sh //启动hadoop文件系统
bin/hive --service metastore & //后台启动hive元数据服务
bin/spark-shell --master local[2] //启动Spark服务
import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._
val ssc = new StreamingContext(sc, Seconds(1)) //以一秒作为一个批次的数据输入
val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
val result = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_,1)).reduceByKey(_+_)
result.print()
ssc.start() // 启动开始进行处理的操作
ssc.awaitTermination() // Wait for the computation to terminate
==============Scala实战Spark Streaming开发========
1、前期准备 Windows搭建好 Spark环境
2、配置Maven的Pom.xml文件
<dependency> <groupId>org.apache.sparkgroupId> <artifactId>spark-streaming_2.10artifactId> <version>${spark.version}version> <scope>compilescope> dependency>
package com.yushu.bigdata.spark.app.streaming
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object StreamingWordCount {
def main(args: Array[String]): Unit = {
// 1. 构建上下文
/**
* 当使用数据接收器的时候,因为在正常job之外需要一直运行一个task,
* 所以需要占用一个线程====>在启动的时候,要求至少给定线程数量为2,一个线程一直接收数据,一个线程处理接收的数据
*/
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName("streaming-wordcount")
val sc = SparkContext.getOrCreate(conf)
/**
* batchDuration: 给定的是批次产生间隔时间,当一个批次产生的时候,
* 这个批次会放入到一个待运行的队列中(先进先出),
* 后台有专门的调度线程负责从待运行队列中获取批次进行执行,
* 但是运行只有在上一个批次执行完成的情况下才能够运行当前批次;
* 如果上一个批次运行的实际比较长,那么当前批次等待的时间也就比较长
* ======> 一般情况要,要求批次产生的间隔大小要比批次的运行时间要大
**/
val ssc = new StreamingContext(sc, Seconds(10))
val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
val result = dstream.flatMap(line => {
line.split(" ")
}).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
result.print()
/**
* TODO
* 调用dstream的saveAsTextFiles结果保存到HDFS上的时候,会出现一个问题:每个批次一个文件夹
*/
result.saveAsTextFiles("result/wc/r")
// 将rdd进行转换操作,然后返回一个新的RDD
dstream
.transform(rdd => {
// TODO: 一个批次只有一个RDD,不要考虑多个RDD的合并之类的问题
rdd.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
})
.foreachRDD(rdd => {
// TODO: 一个批次只有一个RDD,不要考虑多个RDD的合并之类的问题
// 将DStream的数据输出问题,转换为RDD的数据输出
rdd.repartition(1).foreachPartition(iter => iter.foreach(println))
})
// 启动开始进行处理的操作
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}
温馨提示:
除了print()方法将处理后的数据输出之外,还有其他的方法也非常重要,在开发中需要重点掌握,比如SaveAsTextFile,SaveAsHadoopFile等,
最为重要的是foreachRDD方法,这个方法可以将数据写入Redis,DB,DashBoard等,甚至可以随意的定义数据放在哪里,功能非常强大。
import kafka.serializer.StringDecoder
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object KafkaReceiverStreamingWordCount {
def main(args: Array[String]): Unit = {
// 1. 构建上下文
val conf = new SparkConf()
.setMaster("local[10]")
.setAppName("KafkaReceiverStreamingWordCount")
.set("spark.streaming.blockInterval", "1s") //指定bolck块形成的间隔时间,对应RDD的一个分区
val sc = SparkContext.getOrCreate(conf)
val ssc = new StreamingContext(sc, Seconds(10)) //指定多长时间生成一个批次的数据
// 读取DStream
// kafka连接zk的信息
val zkQuorum: String = "bigdata-01.yushu.com:2181/kafka"
// 给定consumer的group id是啥
val groupId: String = "streaming3"
// 给定需要读取的topic名称以及该topic需要使用多少个线程来读取数据
val topics: Map[String, Int] = Map("yushu1" -> 2)
// 给定kafka的consumer的相关配置信息
val kafkaParams = Map[String, String](
"zookeeper.connect" -> zkQuorum,
"group.id" -> groupId,
"zookeeper.connection.timeout.ms" -> "10000",
"auto.offset.reset" -> "smallest")
// 方式一:
// val dstream1 = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
// 方式二:
// val dstream2 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
// 使用Union合并dstream,当receiver的情况下,可以提高数据的处理能力
val dstream21 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
val dstream22 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
val dstream23 = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics, StorageLevel.MEMORY_AND_DISK_SER_2).map(_._2)
val dstream3 = dstream21.union(dstream22).union(dstream23)
val dstream = dstream3
// DStream的操作
val result = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
result.print()
// 启动开始进行处理的操作
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}
import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object KafkaDirectStreamingWordCount {
def main(args: Array[String]): Unit = {
// 1. 构建上下文
val conf = new SparkConf()
.setMaster("local[10]")
.setAppName("KafkaDirectStreamingWordCount")
val sc = SparkContext.getOrCreate(conf)
val ssc = new StreamingContext(sc, Seconds(10))
// 读取DStream
// 给定通过Simple Consumer API连接kafka的时候需要的配置参数: metadata.broker.list,auto.offset.reset
val kafkaParams = Map[String, String](
"metadata.broker.list" -> "bigdata-01.yushu.com:9092,bigdata-01.yushu.com:9093,bigdata-01.yushu.com:9094,
bigdata-01.yushu.com:9095",
"auto.offset.reset" -> "smallest")
// 给定需要读取数据的topic名称
val topics = Set("yushu1")
// 给定读取数据的分区信息语句偏移量的值(需要明确给定读取那些topic的那些分区的数据<从哪个offset开始读取>)
val fromOffsets: Map[TopicAndPartition, Long] = Map(
TopicAndPartition("yushu1", 0) -> 0,
TopicAndPartition("yushu1", 1) -> 100
)
// 给定数据转换函数(定义对于kafka的数据可以进行如何转换操作)
val messageHandler: MessageAndMetadata[String, String] => String = message => {
// TODO:在这里可以获取偏移量、topic名称、分区id、key
/* message.topic
message.partition
message.offset
message.key()*/
message.message()
}
// 方式一
// val dstream1 = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics).map(_._2)
// 方式二
val dstream2 = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, String](ssc, kafkaParams, fromOffsets, messageHandler)
val dstream = dstream2
// DStream的操作
val result = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
result.print()
// 启动开始进行处理的操作
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}
==============================================================import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object HAStreamingWordCount {
def main(args: Array[String]): Unit = {
// 1. 构建上下文
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName("HAStreamingWordCount")
val sc = SparkContext.getOrCreate(conf)
// 一般情况下,该路径为hdfs上的文件夹,第一次是空的或者不存在的
val path = "hdfs://bigdata-01.yushu.com:8020/yushu/spark/streaming/chk/01"
// 构建StreamingContext对象以及DStream的操作, 所以dstream的操作必须放到该函数中
def creatingStreamingContextFunc(): StreamingContext = {
val ssc = new StreamingContext(sc, Seconds(10))
val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
// TODO: 对于修复HA的streaming的程序的bug情况下,不能改变DStream的依赖关系
// TODO: 所以,在HA的streaming应用中,一般使用mapPartitions该API的比较多
val result = dstream.flatMap(line => {
line.split(" ")
}).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
result.print()
// 设置checkpoint的地址
ssc.checkpoint(path)
ssc
}
// 如果checkpointPath对应的文件夹中存储着streaming应用的元数据,进行恢复加载加载;如果没有,就使用给定的函数进行创建操作
val ssc = StreamingContext.getActiveOrCreate(
checkpointPath = path,
creatingFunc = creatingStreamingContextFunc
)
// 启动开始进行处理的操作
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}
foreachRDD API类似transform API,区别在于:foreachRDD没有数据返回值
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object UpdateStateByKeyStreamingWordCount {
def main(args: Array[String]): Unit = {
// 1. 构建上下文
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName("UpdateStateByKeyStreamingWordCount")
val sc = SparkContext.getOrCreate(conf)
val ssc = new StreamingContext(sc, Seconds(10))
// 在使用updateStateByKey的时候必须给定
ssc.checkpoint(s"hdfs://bigdata-01.yushu.com:8020/yushu/spark/streaming/chk/${System.currentTimeMillis()}")
val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)//监听该主机 该端口的数据
// 当前批次的结果
val wordCountDStream = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).reduceByKey(_ + _)
// 对之前的状态值和当前批次的结果进行合并/聚合操作
/**
* def updateStateByKey[S: ClassTag](
* updateFunc: (Seq[V], Option[S]) => Option[S]
* ): DStream[(K, S)]
* 功能:对当前批次的数据和之前的状态信息按照key进行分组后,对value的数据进行聚合操作
* Seq[V]和Option[S]: 对应是同一个key
* Seq[V]: 对应的是当前批次中某一个key分组后所有values的值
* Option[S]:对应的是之前执行过程中某一个key对应的状态信息, 如果之前没有状态信息,值为None
* updateFunc函数的返回值是某一个key经过当前批次执行后的需要保存的状态信息,也值执行结果
*/
val result = wordCountDStream.updateStateByKey((values: Seq[Int], stats: Option[Long]) => {
// 聚合当前状态的数据, 如果当前批次没有对应的key值,那么values这个序列为空
val currentValue = values.sum
// 获取上一个状态的值
val preValue = stats.getOrElse(0L)
// 更新状态值,并返回新的状态值
Some(preValue + currentValue)
})
result.print()
// 启动开始进行处理的操作
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}
=================WindowStreamingWordCount===================
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object WindowStreamingWordCount {
def main(args: Array[String]): Unit = {
// 1. 构建上下文
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName("WindowStreamingWordCount")
val sc = SparkContext.getOrCreate(conf)
val ssc = new StreamingContext(sc, Seconds(1))
ssc.checkpoint(s"hdfs://bigdata-01.yushu.com:8020/yushu/spark/streaming/chk/${System.currentTimeMillis()}")
val dstream = ssc.socketTextStream("bigdata-01.yushu.com", 9999)
val wordDStream = dstream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1))
val result = wordDStream.reduceByKeyAndWindow(
(a: Int, b: Int) => a + b, // 指定按照key分组后,数据聚合的函数
(c: Int, d: Int) => c - d, // c指的是上一个执行批次的结果,d是上一个执行批次和当前执行批次没有重叠的那一部分(上一个执行批次中)
Seconds(5), // windowDuration: Duration ===> 指定形成的新DStream所包含的时间范围,也就是指定需要计算最近多久的数据;要求该值必须是父DStream的批次产生时间的整数倍
Seconds(3) // slideDuration: Duration ===> 指定新的DStream多久产生一个批次,也就是多久执行一次;要求该值必须是父DStream的批次产生时间的整数倍
)
result.print()
// 启动开始进行处理的操作
ssc.start() // Start the computation
ssc.awaitTermination() // Wait for the computation to terminate
}
}