spark-streaming笔记

SparkStreaming笔记

框架的类型:
1.离线批处理:mapreduce、hive、SparkCore、Sparksql =》 mapreduce spark
2.SQL的交互式查询:hive、SparkSQL
3.流式框架:flume、kafka、SparkStreaming
4.实时计算:SparkStreaming

Strom(Clojure编写的)/jStrom(java编写的)
完全实时的流式数据处理平台
来一条数据就立马计算一条数据,在高并发的情况下,对机器的性能要求很高
如果配置低了,那么就是出现很高的延迟

维护strom的集群的成本要比维护同等级别的Spark集群的成本要高出很多

SparkStreaming:(以微批来模拟流式)
准实时的流式数据处理平台
sparkstreaming是按照一个一个批次数据来进行计算的,只有当上一个批次完全计算完毕,才会计算下一个批次。否则,就会处于阻塞的状态
问题:
批次是如何产生的呢???
每个批次产生的时间是由用户指定的,每到指定的时间,就会产生一个批次,这段时间内接收到的数据,就是这个批次中需要计算的数据

SparkStreaming程序数据处理流程:
1.读取数据
读取数据形成DStream
2.数据处理
调用DStream的API或者是将DStream转成RDD/DataFrame
3.数据输出
文件系统
RDBMS(关系型数据库)
hive、hbase。。。
Kafka

=================================================================
程序的入口:
SparkCore:SparkContext
SparkSQL: SparkSession (基于SparkContext)
SparkStreaming:StreamingContext(基于SparkContext)

核心抽象:
SparkCore:RDD (弹性分布式数据集)
SparkSQL: DataFrame (以RDD为基础的分布式数据集二维表格)
SparkStreaming:DStream(Discretized Streams)(离散化的流)

小案例:
-1.在maven项目的pom文件中添加SparkStreaming的依赖

org.apache.spark
spark-streaming_2.11
2.2.1

-2.使用SparkStreaming程序读取socket的传输的数据,然后计算词频统计,最后在控制台打印
-2.1安装netcat
方式1:离线安装
-1.将nc-1.84-22.el6.x86_64.rpm上传到linux
-2.使用root用户安装nc-1.84-22.el6.x86_64.rpm
命令:rpm -ivh nc-1.84-22.el6.x86_64.rpm
安装成功之后,退出root,exit
方式2:在线安装
-1.切换root用户
-2.使用yum命令:yum install -y nc
安装成功之后,退出root,exit
-2.2nc安装结束之后,验证是否成功
命令:nc -lk 99999

-2.3开始编写代码

代码:
package com.bigdata.SparkStreaming

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object SparkStreamingSocketDemo {

def main(args: Array[String]): Unit = {

//1.构建上下文 ==》 StreamingContext 基于 SparkContext
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("SparkStreamingSocketDemo")

val sc = SparkContext.getOrCreate(config)

//def this(sparkContext: SparkContext, batchDuration: Duration)
//通过查看源码可知,构建StreamingContext需要传这两个参数
//batchDuration:用户指定的批次产生的间隔时间
val ssc  = new StreamingContext(sc,Seconds(5))

//2.读取数据形成DStream

/* def socketTextStream(
hostname: String,
port: Int,
storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2
)*/
val dstream: ReceiverInputDStream[String] = ssc.socketTextStream(“superman-bigdata.com”,9999)

//3.数据处理,计算词频统计
//hello \t spark \t hbase
val result: DStream[(String, Int)] = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word,1))
  .reduceByKey(_ + _)

//4.数据的输出
result.print()

//5.开启流式程序,如果上一个批次没有计算结束,那么下一个批次处于阻塞
ssc.start()
ssc.awaitTermination()

}
}
-2.4先在linux中执行以下命令:
nc -lk 9999 回车
-2.5运行IDEA中的代码
注意:如果你觉得代码在运行的时候,控制台的日志过多,导致看不清
你可以调整日志等级
-1.修改项目中的log4j文件
log4j.rootCategory=ERROR,console
-2.直接编写代码修改等级
sc.setLogLevel(“ERROR”)

=================================================================
SparkStreaming程序的运行原理:
两种数据的接受方式:
第一种方式:使用数据接收器的方式(receiver)
1.数据接收器接收的输入的数据,按照给定批次产生的间隔时间,然后产生一个一个的批次,批次内部是以block块的形式进行保存(保存在内存或者磁盘,缓存等级决定内存还是磁盘)。
block块的产生也是基于时间的。默认每200ms形成一个block块。
我们可以使用这个参数来修改block块产生的时间隔间:
spark.streaming.blockInterval = 200ms
spark的应用参数,可以写在三个地方:
-1.写在spark-default.conf文件中(永久生效,一般写的是通用参数)
-2.写在代码中,SparkConf下面 (每个程序中才会生效,一次性的)
-3.在执行bin/spark-submit的命令后面加上参数 (提交的应用程序才会生效,一次性的)

		而且批次就是一个RDD,block块其实就是分区。
		比如:批次产生的间隔时间是5秒钟,那么默认情况下,每个批次中有25个block块
		也就是说当前RDD中存在25个分区。那么通过之前学习的SparkCore,我们可以知道一个分区其实就是一个task任务,那么批次中有多少个block块,其实就有多少个task任务。
		
		注意:
			-1.在receiver的模式下,block的个数的确是当前批次的分区数。但是其实只有存在block的时候,才会有分区,也就是说只有数据源源不断的接受,才会形成稳定的分区数。如果没有数据,那么分区数就是0,如果数据不多,而且不稳定,那么分区数是在变化的
			
			-2.基于receiver数据接收器的模式下,数据接受和数据处理其实是分开的,是不同的线程计算的。必须要有一个独立的线程负责接收数据,所以receiver模式下,线程数必须要大于2
			
			不管计算的批次是否阻塞,接受数据的线程永远会按照给定的时间间隔产生对应的批次,所以接受器的线程和计算的线程是相对独立的,互不影响的
			
		2.bacth和RDD的产生
			每隔一个批次产生的时间就会产生一个批次,这个批次中的数据,就是这段时间内接收器接收到的数据
			每个批次实质上就是一个RDD
			一个批次对应一个RDD
			一个批次中的block对应就是RDD中的分区
			所以block的个数,就是RDD的分区数。
		
	第二种方式:直接读取数据(direct)
		使用direct模式,每隔一个批次的时间,产生一个批次,但是因为没有数据接收器,所以也就不存在block。直接将数据的元数据信息,保存在RDD中。RDD的分区数是由数据源的分区数决定的
		比如:sparkstreaming使用direct模式,接受kafka中的beifeng0topic中的数据,那么每个批次也就是每个RDD的分区数应该是topic的分区数。

请注意:不管是receiver模式,还是direct模式,我们说的分区数都是第一次形成DStream流的时候的分区数
如果这条流已经开始计算了,那么分区数就会发生改变,而不是我们以上说的情况了

=================================================================
DStream(Discretized Streams)(离散化的流)
DStream底层其实就是一系列的RDD和时间组成的集合流 (微批模拟流)
DStream有三大特性:

  • A DStream internally is characterized by a few basic properties:

    • A list of other DStreams that the DStream depends on
      DStream之间是相互依赖的
    • A time interval at which the DStream generates an RDD
      DStream会间隔性的产生RDD(其实:每隔一个批次产生的时间,就会产生一个RDD)
    • A function that is used to generate an RDD after each time interval
      会有一个专门函数:compute

=======================================================================================
Input DStream输入流,也就是SparkStreaming的数据源
官方文档:
http://spark.apache.org/docs/2.2.1/streaming-programming-guide.html#input-dstreams-and-receivers

Spark Streaming provides two categories of built-in streaming sources.
SparkStreaming提供了内置的两种数据源

1.Basic sources(基础源): Sources directly available in the StreamingContext API. Examples: file systems, and socket connections.
这些源一般都是在StreamingContextAPI下面自带的一些方法。
比如以下API就是SparkStreaming的基础源(org.apache.spark.streaming.StreamingContext下的一些读取数据的方法)
-1.def socketTextStream(
hostname: String,
port: Int,
storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2
): ReceiverInputDStream[String]

-2.def socketStream[T: ClassTag](
hostname: String,
port: Int,
converter: (InputStream) => Iterator[T],
storageLevel: StorageLevel
): ReceiverInputDStream[T]

-3.def rawSocketStream[T: ClassTag](
hostname: String,
port: Int,
storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2
): ReceiverInputDStream[T]

-4.def fileStream[
K: ClassTag,
V: ClassTag,
F <: NewInputFormat[K, V]: ClassTag
] (directory: String): InputDStream[(K, V)]

以上采用的都是基于数据接收器的

Advanced sources(额外的数据源): Sources like Kafka, Flume, Kinesis, etc. are available through extra utility classes. These require linking against extra dependencies as discussed in the linking section.

Kafka: Spark Streaming 2.2.1 is compatible with Kafka broker versions 0.8.2.1 or higher. See the Kafka Integration Guide for more details.

Flume: Spark Streaming 2.2.1 is compatible with Flume 1.6.0. See the Flume Integration Guide for more details.

===================================================================================================
Kafka和SparkStreaming的集成:
官方集成指南:spark.apache.org/docs/2.2.1/streaming-kafka-integration.html

问题:
SparkStreaming的版本是2.2.1,Kafka的版本是0.11.0.1
那么,SparkStreaming和Kafka集成肯定是需要集成包的
集成包使用什么版本呢?
SparkStreaming和Kafka的集成包有两个版本:
spark-streaming-kafka-0-8,spark-streaming-kafka-0-10
这两个版本,我们都要讲。

===================================================================================================
SparkStreaming和Kafka的集成包:spark-streaming-kafka-0-8
文档地址:http://spark.apache.org/docs/2.2.1/streaming-kafka-0-8-integration.html
集成方式有两种:
1.基于数据接收器的receiver模式
2.直接读取数据的模式

1.基于数据接收器的receiver模式
第一步:在pom文件中添加依赖

org.apache.spark
spark-streaming-kafka-0-8_2.11
2.2.1

第二步:选择输入源的API,编写代码 使用KafkaUtils类中的方法创建流
def createStream(
ssc: StreamingContext, //上下文
zkQuorum: String, //zookeeper的集群地址以及元数据保存的目录
groupId: String, 消费者的组ID
topics: Map[String, Int], 待读取的topic以及消费topic需要的线程数
storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2
): ReceiverInputDStream[(String, String)]

代码:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object Receiver01 {
def main(args: Array[String]): Unit = {
//1.构建上下文
val config = new SparkConf()
.setMaster(“local[*]”)
.setAppName(“Receiver01”)
.set(“spark.streaming.blockInterval”,“1000”)

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(5))

//2.读取数据形成DStream  => 使用receiver模式,这个代码是写死的,无法传入kafka的消费者的配置参数
//这个代码的消费者的偏移量是直接保存在zookeeper里面的
val zkQuorum = "superman-bigdata.com:2181/yangpu1005"
val groupId = "xiaoming"
val topics = Map(
  "receiver" -> 4
)

val dstream: DStream[String] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics).map(t => t._2)
dstream.foreachRDD(rdd => {
  println(rdd.partitions.size)
})

//3.计算数据
val result = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word, 1))
  .reduceByKey((a, b) => a + b)

//4.结果输出
result.print()

//5.开启程序
ssc.start()
ssc.awaitTermination()

}
}

def createStream[K: ClassTag, V: ClassTag, U <: Decoder[]: ClassTag, T <: Decoder[]: ClassTag](
ssc: StreamingContext, //上下文
kafkaParams: Map[String, String], //kafka消费者的配置参数
topics: Map[String, Int], //待读取的topic以及消费topic需要的线程数
storageLevel: StorageLevel //缓存等级
): ReceiverInputDStream[(K, V)]
代码:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import kafka.serializer.StringDecoder
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object Receiver02 {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("Receiver01")
  .set("spark.streaming.blockInterval","1000")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(5))

//2.读取数据形成DStream  ==》 使用receiver模式
//报错:Wrong value earliest of auto.offset.reset in ConsumerConfig; Valid values are smallest and largest
//earliest和latest这个是新版本中的参数值  老版本中的应该是smallest and largest
val kafkaParams = Map(
  "zookeeper.connect" -> "superman-bigdata.com:2181/yangpu1005",
  "group.id" -> "xiaohong",
  "auto.offset.reset" -> "smallest"
)
val topics = Map(
  "receiver" -> 4
)
val storageLevel =StorageLevel.MEMORY_AND_DISK
val dstream: DStream[String] = KafkaUtils.createStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics,storageLevel).map(t => t._2)

//3.计算数据
val result = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word, 1))
  .reduceByKey((a, b) => a + b)

//4.结果输出
result.print()

//5.开启程序
ssc.start()
ssc.awaitTermination()

}
}

===================================================================================================
2.直接读取数据的模式(Direct)
注意:direct模式下,偏移量是不会直接保存到zookeepr中的
第一步:在pom文件中添加依赖

org.apache.spark
spark-streaming-kafka-0-8_2.11
2.2.1

第二步:选择输入源的API,编写代码 使用KafkaUtils类中的方法创建流
def createDirectStream[
K: ClassTag,
V: ClassTag,
KD <: Decoder[K]: ClassTag,
VD <: Decoder[V]: ClassTag] (
ssc: StreamingContext, //上下文
kafkaParams: Map[String, String], //kafka消费者的配置参数
topics: Set[String] //待消费的topic
): InputDStream[(K, V)]
代码:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import kafka.serializer.StringDecoder
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object Direct01 {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("Direct01")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(5))

//2.读取数据形成DStream ==> Direct模式是不需要连接zookeeper。偏移量不会写入zookeeper中,而且也不会做任何保存操作

//所以当我们使用direct的时候,需要自己去保存偏移量信息
val kafkaParams = Map(
“metadata.broker.list” -> “superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095”,
“group.id” -> “xiaohei”,
“auto.offset.reset” -> “largest” //默认从最新开始消费 largest
)
val topics = Set(“direct”)

val dstream: DStream[String] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics).map(t => t._2)
//我们说direct模式下,第一次的时候,dstream中的分区数应该和topic的分区数一致
//我们知道direct这个topic的分区数我设置的是4个
//那么下面代码打印的应该也是4
dstream
  .foreachRDD(rdd => {
    println(rdd.partitions.size)
  })

//3.计算数据
val result = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word, 1))
  .reduceByKey((a, b) => a + b)

//4.结果输出
result.print()

//5.开启程序
ssc.start()
ssc.awaitTermination()

}
}

def createDirectStream[
K: ClassTag,
V: ClassTag,
KD <: Decoder[K]: ClassTag,
VD <: Decoder[V]: ClassTag,
R: ClassTag] (
ssc: StreamingContext, //上下文
kafkaParams: Map[String, String], //kafka消费者的配置参数
fromOffsets: Map[TopicAndPartition, Long], //指定消费的topic,以及指定从哪一个分区的哪一个偏移量位置开始消费
messageHandler: MessageAndMetadata[K, V] => R //数据以及元数据信息
): InputDStream[R]
代码:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object Direct02 {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("Direct02")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(5))

//2.读取数据形成DStream
val kafkaParams = Map(
  "metadata.broker.list" -> "superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095",
  "group.id" -> "xiaobai"
)

val fromOffsets = Map(
  //1.可以只写一个或者几个分区 2.指定的偏移量的值不可以超过当前分区的数据条数
  //报错:java.lang.IllegalArgumentException: requirement failed:  numRecordsmust not be negative
  //这个报错就是因为指定的偏移量超过了当前分区的数据条数,导致的
  TopicAndPartition("direct",0) -> 2L,
  TopicAndPartition("direct",1) -> 2L,
  TopicAndPartition("direct",2) -> 3L,
  TopicAndPartition("direct",3) -> 3L
)

val messageHandler: MessageAndMetadata[String, String] => String = (msg:MessageAndMetadata[String,String]) => {
  val topicName = msg.topic
  val partitionID = msg.partition
  val offset = msg.offset
  val value = msg.message()

  val sb = new StringBuilder
  sb.append("topicName=").append(topicName).append(",")
    .append("partitionID=").append(partitionID).append(",")
    .append("offset=").append(offset).append(",")
    .append("value=").append(value)
 sb.toString()
}

val dstream = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder,String](ssc,kafkaParams,fromOffsets,messageHandler)

//3.结果输出
dstream.print()

//4.开启程序
ssc.start()
ssc.awaitTermination()

}
}

//因为kafka的direct模式下,不会对消费者的偏移量做什么保存操作,因此,同一个消费者的情况下,我们无法确定上一次消费的记录,所以我们接下来学习手动保存偏移量记录,保存到mysql数据库中
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import java.sql.{Connection, DriverManager}

import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object Direct02 {
def main(args: Array[String]): Unit = {
//1.构建上下文
val config = new SparkConf()
.setMaster(“local[*]”)
.setAppName(“Direct02”)

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(5))

//2.读取数据形成DStream
val kafkaParams = Map(
  "metadata.broker.list" -> "superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095",
  "group.id" -> "xiaobai"
)

val fromOffsets = Map(
  //1.可以只写一个或者几个分区 2.指定的偏移量的值不可以超过当前分区的数据条数
  //报错:java.lang.IllegalArgumentException: requirement failed:  numRecordsmust not be negative
  //这个报错就是因为指定的偏移量超过了当前分区的数据条数,导致的
  TopicAndPartition("direct",0) -> 2L,
  TopicAndPartition("direct",1) -> 2L,
  TopicAndPartition("direct",2) -> 3L,
  TopicAndPartition("direct",3) -> 3L
)

val messageHandler = (msg:MessageAndMetadata[String,String]) => {
  val topicName = msg.topic
  val partitionID = msg.partition
  val offset = msg.offset
  val value = msg.message()

  //偏移量记录插入数据库
  insertOffsetIntoMysql(topicName,partitionID,offset)

  val sb = new StringBuilder
  sb.append("topicName=").append(topicName).append(",")
    .append("partitionID=").append(partitionID).append(",")
    .append("offset=").append(offset).append(",")
    .append("value=").append(value)
 sb.toString()
}

val dstream = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder,String](ssc,kafkaParams,fromOffsets,messageHandler)

//3.结果输出
dstream.print()

//4.开启程序
ssc.start()
ssc.awaitTermination()

}

def insertOffsetIntoMysql(topicName:String,partitionID:Int,offset:Long)={

//1.使用JDBC原生态的方式连接数据库
val url = "jdbc:mysql://superman-bigdata.com:3306/yangpu1005"
val user = "root"
val password = "123456"
var connect = DriverManager.getConnection(url,user,password)

//插入的sql语句
val sql = "replace into ManualOffsetConsumer values (?,?,?)"
var pstmt = connect.prepareStatement(sql)

pstmt.setString(1,topicName)
pstmt.setInt(2,partitionID)
pstmt.setLong(3,offset)
pstmt.executeUpdate()

pstmt.close()
connect.close()

}

}

===================================================================================================
SparkStreaming和Kafka集成的代码优化:
1.receiver模式下的优化方式:
-1.设置分区数,我们知道数据量越大,分区数要越大,那么在receiver模式下,如何调整分区数
控制block块的个数:spark.streaming.blockInterval 默认200ms 注意,这个值必须是批次间隔时间的整数倍
-2.当发现接受的速率比较慢,可以考虑给定多个数据接收器
比如:
val dstream1: DStream[String] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics).map(t => t._2)
val dstream2: DStream[String] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics).map(t => t._2)
val dstream3: DStream[String] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics).map(t => t._2)
val dstream4: DStream[String] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics).map(t => t._2)

		val dstream = dstream1.union(dstream2).union(dstream3).union(dstream4)
	
!!-3.当接受的速度太快了,根本来不及计算。产生严重的延迟情况,甚至导致程序失败
		使用背压机制来解决这个问题
		spark.streaming.backpressure.enabled = true  开启背压机制,默认是关闭的
		spark.streaming.receiver.maxRate 控制每秒每个分区中接受的最大的处理条数
		例子:val config = new SparkConf()
			  .setMaster("local[*]")
			  .setAppName("Receiver01")
			  .set("spark.streaming.blockInterval","1000")
			  .set("spark.streaming.backpressure.enabled","true" )
			  .set("spark.streaming.receiver.maxRate","5") //每秒每个分区中处理5条数据

2.direct模式下的优化方式
-1.不管你是receiver还是direct模式下,都可以开启的
开启动态资源分配
使用场景:当程序出现job的高峰和低谷的时候,动态的控制executor的数量
spark.dynamicAllocation.enabled 默认是false 设置为true 开启动态资源调度
spark.dynamicAllocation.initialExecutors 表示executor的初始的个数
spark.dynamicAllocation.maxExecutors 表示executor浮动变化的最大的个数
spark.dynamicAllocation.minExecutors 表示executor浮动变化的最小的个数

-2.当接受的速度太快了,根本来不及计算。产生严重的延迟情况,甚至导致程序失败
		使用背压机制来解决这个问题
		spark.streaming.backpressure.enabled = true  开启背压机制,默认是关闭的
		spark.streaming.kafka.maxRatePerPartition  每个分区每秒钟处理的最大的数据条数,不设置表示没有任何的限制
		例子:    val config = new SparkConf()
					  .setMaster("local[2]")
					  .setAppName("Direct01")
					  .set("spark.streaming.backpressure.enabled","true")
					  .set("spark.streaming.kafka.maxRatePerPartition","3")

===================================================================================================
!!!!!!检查点机制:checkpoint
官方文档:
http://spark.apache.org/docs/2.2.1/streaming-programming-guide.html#checkpointing
作用:当程序还没有完全执行结束,程序突然中止了。那么我们再次提交程序的时候,我们希望程序不是从头开始执行的,而是从上一次结束的地方继续运行,那么我们可以使用检查点机制,来进行应用的恢复。
例子:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import kafka.serializer.StringDecoder
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils

object Checkpoint {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[2]")
  .setAppName("Direct01")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(3))

//设置检查点机制
val path = "hdfs://superman-bigdata.com:9000/yangpu1005/checkpoint"
ssc.checkpoint(path)

//2.读取数据形成DStream ==> Direct模式是不需要连接zookeeper。偏移量不会写入zookeeper中,而且也不会做任何保存操作
//所以当我们使用direct的时候,需要自己去保存偏移量信息
val kafkaParams = Map(
  "metadata.broker.list" -> "superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095",
  "group.id" -> "hello",
  "auto.offset.reset" -> "smallest" //默认从最新开始消费  largest
)
val topics = Set("direct")

val dstream: DStream[String] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics).map(t => t._2)

//3.计算数据
val result = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word, 1))
  .reduceByKey((a, b) => a + b)

//4.结果输出
result.print()

//5.开启程序
ssc.start()
ssc.awaitTermination()

}
}

===================================================================================================
Direct模式下,因为偏移量是不做任何保存操作的,所以我们不知道怎么去查看之前消费者的偏移量记录
所以我们必须手动进行偏移量的保存
在direct模式下,我们可以使用一下两种方式保存偏移量
-1.def createDirectStream[
K: ClassTag,
V: ClassTag,
KD <: Decoder[K]: ClassTag,
VD <: Decoder[V]: ClassTag,
R: ClassTag] (
ssc: StreamingContext, //上下文
kafkaParams: Map[String, String], //kafka消费者的配置参数
fromOffsets: Map[TopicAndPartition, Long], //指定消费的topic,以及指定从哪一个分区的哪一个偏移量位置开始消费
messageHandler: MessageAndMetadata[K, V] => R //数据以及元数据信息
): InputDStream[R]
我们可以在messageHandler对象中进行偏移量的保存,保存到mysql中

-2.def createDirectStream[
K: ClassTag,
V: ClassTag,
KD <: Decoder[K]: ClassTag,
VD <: Decoder[V]: ClassTag] (
ssc: StreamingContext, //上下文
kafkaParams: Map[String, String], //kafka消费者的配置参数
topics: Set[String] //待消费的topic
): InputDStream[(K, V)]
这种代码呢,不能执行在以上代码中确定偏移量的保存
但是可以直接使用DStream转成RDD,来获取每个RDD中元数据信息来进行保存

栗子:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_8

import kafka.serializer.StringDecoder
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.{HasOffsetRanges, KafkaUtils, OffsetRange}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object Direct_SaveOffset_Common {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[2]")
  .setAppName("Direct_SaveOffset_Common")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc, Seconds(1))

//2.读取数据形成DStream
val kafkaParams = Map(
  "metadata.broker.list" -> "superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095",
  "group.id" -> "xiaoxing",
  "auto.offset.reset" -> "smallest" //默认从最新开始消费  largest
)
val topics = Set("direct")

val dstream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics)
//报错:java.lang.ClassCastException: org.apache.spark.rdd.MapPartitionsRDD cannot be cast to org.apache.spark.streaming.kafka.HasOffsetRanges
//表示类型不一致导致的,我们不需要取出value的值,直接保存原始的流


//3.结果数据
dstream.map(t => t._2).print()

//4.偏移量的手动保存
//构建一个数组进行偏移量的保存
var  offsetRanges: Array[OffsetRange] =  Array.empty
dstream
  .foreachRDD(rdd  => {
    offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges //得到的当前RDD中所有数据的偏移量的范围

    val arr: Array[OffsetRange] = offsetRanges.toSet.toArray

    for(msg <- arr){
      val topicName = msg.topic
      val partitionID = msg.partition
      val fromOffsets = msg.fromOffset
      val untilOffsets = msg.untilOffset

      println("topicName=" + topicName + "," + "partitionID=" + partitionID + "," + "fromOffsets=" + fromOffsets + "," + "untilOffsets=" + untilOffsets )
      //作业:使用原生态的JDBC代码自己写入数据库中
    }
  })

//5.开启程序
ssc.start()
ssc.awaitTermination()

}
}

===================================================================================================
SparkStreaming和Kafka的集成包:spark-streaming-kafka-0-10
官方文档:
http://spark.apache.org/docs/2.2.1/streaming-kafka-0-10-integration.html

-1.在pom文件中添加依赖

org.apache.spark
spark-streaming-kafka-0-10_2.11
2.2.1

-2.注意:spark-streaming-kafka-0-10在这个集成包的下面
sparkstreaming和kafka集成只有一种方式,那就是direct模式
而且这个版本的消费者偏移量和zookeeper没有任何关系!!!!!

在这个版本下:
消费者的偏移量管理有两种方式:
-1.偏移量自动提交,保存在本地
“enable.auto.commit” -> “true” 表示偏移量是程序自动提交在本地保存的

-2.手动管理偏移量
	"enable.auto.commit" -> "false" 表示消费者的偏移量是没有任何保存的,必须要手动去储存偏移量

案例:
-1.偏移量自动提交,保存在本地
“enable.auto.commit” -> “true” 表示偏移量是程序自动提交在本地保存的
def createDirectStream[K, V](
ssc: StreamingContext,
locationStrategy: LocationStrategy,
consumerStrategy: ConsumerStrategy[K, V]
): InputDStream[ConsumerRecord[K, V]]
代码:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_10

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.KafkaUtils
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object Auto_Offset_Commit {

def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("Auto_Offset_Commit")

val sc = SparkContext.getOrCreate(config)

val ssc = new StreamingContext(sc,Seconds(5))

//2.读取数据形成DStream
//报错:org.apache.kafka.common.config.ConfigException:
// Missing required configuration "key.deserializer" which has no default value.

val topics = Array("direct")
val kafkaParams = Map(
  "bootstrap.servers" -> "superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095",
  "group.id" -> "zhangsan", //消费者组ID
  "enable.auto.commit" -> "true", //表示偏移量自动保存
  "auto.offset.reset" -> "earliest",  // 从最新还是最老开始消费  earliest /lagest
  "key.deserializer" -> classOf[StringDeserializer], //key的序列化
  "value.deserializer" -> classOf[StringDeserializer] //value的序列化
)

val dstream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String,String](ssc,PreferConsistent,Subscribe[String,String](topics,kafkaParams))

val result: DStream[(String, Int, Long)] = dstream
  .map(record => {
    val topicName = record.topic()
    val partitionID = record.partition()
    val offset = record.offset()
    val value = record.value()
    (topicName,partitionID,offset)
  })

//3.打印结果
result.print()

//4.开始运行程序
ssc.start()
ssc.awaitTermination()

}
}

-2.手动管理偏移量
“enable.auto.commit” -> “false” 表示消费者的偏移量是没有任何保存的,必须要手动去储存偏移量
代码:
package com.bigdata.SparkStreaming.Kafka_Streaming_0_10

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{HasOffsetRanges, OffsetRange}
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
import org.apache.spark.streaming.kafka010.KafkaUtils
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.{SparkConf, SparkContext, TaskContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object Manual_Offset_Commit {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("Manual_Offset_Commit")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc,Seconds(5))

//2.读取数据形成DStream
val topics = Array("direct")

val kafkaParams = Map(
  "bootstrap.servers" -> "superman-bigdata.com:9092,superman-bigdata.com:9093,superman-bigdata.com:9094,superman-bigdata.com:9095",
  "group.id" -> "lisi", //消费者组ID
  "enable.auto.commit" -> "false", //表示偏移量不自动保存
  "key.deserializer" -> classOf[StringDeserializer], //key的序列化
  "value.deserializer" -> classOf[StringDeserializer] //value的序列化
)

val offset = Map(
  new TopicPartition("direct",0) -> 2L,
  new TopicPartition("direct",1) -> 2L,
  new TopicPartition("direct",2) -> 3L,
  new TopicPartition("direct",3) -> 3L
)

val dstream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String,String](ssc,PreferConsistent,Subscribe[String,String](topics,kafkaParams,offset))

val result: DStream[(String, Int, Long)] = dstream
  .map(record => {
      val topicName = record.topic()
      val partitionID = record.partition()
      val offset = record.offset()
      val value = record.value()
(topicName,partitionID,offset)

})

//3.打印结果
result.print()

//4.进行手动的储存消费者的偏移量信息
dstream
  .foreachRDD(rdd => {
    val offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
    rdd.foreachPartition(iter => {
        val o: OffsetRange = offsetRanges(TaskContext.get().partitionId())
      println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")

      //作业:使用原生态的JDBC代码,写入数据库中
    })
  })

//5.开启程序
ssc.start()
ssc.awaitTermination()

}
}

===================================================================================================
SparkStreaming的一些特殊的方法:
1.transform
2.foreachRDD
3.updateStateByKey
4.Window

1.transform =》 转换函数
transform这个函数用于将DStream转成RDD,然后使用RDD的api进行数据处理,处理完之后,仍然返回DStream
一般用于数据转换
代码:
package com.bigdata.SparkStreaming

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object SparkStreaming_Transform {

def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("SparkStreaming_Transform")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val spark = SparkSession.builder().master("local[*]").appName("SparkStreaming_Transform").getOrCreate()

val ssc = new StreamingContext(sc,Seconds(5))

//2.读取数据形成DStream
val dstream: ReceiverInputDStream[String] =  ssc.socketTextStream("superman-bigdata.com",9999)

//3.使用transform
val result1  = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word,1))
  .reduceByKey((a,b) => a + b)

val result2: DStream[(String, Int)] = dstream
  .transform(rdd => {
    val rdd1: RDD[(String, Int)] = rdd
      .flatMap(line => line.split("\t"))
      .filter(word => word.nonEmpty)
      .map(word => (word,1))
      .reduceByKey((a,b) => a + b)

    //将RDD转成DataFrame
    import  spark.implicits._
    val df = rdd1.toDF("word","count")
    df.show()
    rdd1
  })

result1.print()
result2.print()

//4.开启程序
ssc.start()
ssc.awaitTermination()

}
}

2.foreachRDD ==》 数据输出
这个函数也是将DStream转成RDD ,但是一般用在数据的输出
代码:
package com.bigdata.SparkStreaming

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.ReceiverInputDStream

object SparkStreaming_ForeachRDD {
def main(args: Array[String]): Unit = {

//1.构建上下文
val config = new SparkConf()
  .setMaster("local[*]")
  .setAppName("SparkStreaming_ForeachRDD")

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val spark = SparkSession.builder().master("local[*]").appName("SparkStreaming_ForeachRDD").getOrCreate()

val ssc = new StreamingContext(sc,Seconds(5))

//2.读取数据形成DStream
val dstream: ReceiverInputDStream[String] =  ssc.socketTextStream("superman-bigdata.com",9999)

//3.使用foreachRDD
dstream
  .foreachRDD(rdd => {
    val rdd1: RDD[(String, Int)] = rdd
      .flatMap(line => line.split("\t"))
      .filter(word => word.nonEmpty)
      .map(word => (word,1))
      .reduceByKey((a,b) => a + b)

    import spark.implicits._
    val df = rdd1.toDF("word","count")
    df.show()
    df.write.mode("append").format("json").save("hdfs://superman-bigdata.com:9000/yangpu1005/foreachRDD")
  })

ssc.start()
ssc.awaitTermination()

}
}

===================================================================================================
3.updateStateByKey
应用场景:实时累计功能:
报错:java.lang.IllegalArgumentException: requirement failed: The checkpoint directory has not been set. Please set it by StreamingContext.checkpoint().
表示没有设置checkpiont(元数据保存)
为什么使用updateStateByKey的时候,需要设置checkpoint机制????
1.checkpoint是用来保存之前批次的所有的元数据信息
2.普通的sparkstreaming程序,只能获得当前批次的信息
所以我们必须要设置checkpoint,因为程序需要知道之前的批次的运行状态值

代码:
package com.bigdata.SparkStreaming

import java.util.Properties

import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.{HTable, Put, Result}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableOutputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.mapreduce.Job
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

object SparkStreaming_UpdateStateByKey {

def main(args: Array[String]): Unit = {
//1.构建上下文
val config = new SparkConf()
.setMaster(“local[*]”)
.setAppName(“SparkStreaming_UpdateStateByKey”)

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val spark = SparkSession.builder().master("local[*]").appName("SparkStreaming_UpdateStateByKey").enableHiveSupport().getOrCreate()

val ssc = new StreamingContext(sc,Seconds(5))

//checkpoint 检查点机制
val path = "hdfs://superman-bigdata.com:9000/yangpu1005/checkpoint"
ssc.checkpoint(path)

//2.读取数据形成DStream
val dstream: ReceiverInputDStream[String] =  ssc.socketTextStream("superman-bigdata.com",9999)

//3.使用updateStateByKey实现实时累计效果
//  def updateStateByKey(updateFunc: (Seq[V], Option[S]) => Option[S]): DStream[(K, S)]
//seq:Seq[V]:当前批次的数据
//state:Option[S]:上一个批次的状态值
val result  = dstream
  .flatMap(line => line.split("\t"))
  .filter(word => word.nonEmpty)
  .map(word => (word,1))
  .reduceByKey((a,b) => a + b)  //(word,count)
  .updateStateByKey(
  (seq:Seq[Int],state:Option[Int]) => {
    //seq:表示当前批次的key的value值
    //state:表示上一个批次的key的value的状态值
    val currentValue = seq.sum
    val preValue = state.getOrElse(0)
    Some(currentValue + preValue)
  }
)


//4.结果输出打印
result.print()

//5.将结果保存到外部储存系统
//5.1保存到本地windows的文件系统
result.saveAsTextFiles("file:///D:\\data\\updateStateByKey")

//5.2保存到HDFS的分布式文件系统
result.saveAsTextFiles("hdfs://superman-bigdata.com:9000/yangpu1005/updateStateByKey")

//5.3保存到hive表中
result.foreachRDD(rdd => {
  import spark.implicits._
  rdd.toDF("word","count")
    .write
    .mode("overwrite")
    .format("json")
    .saveAsTable("yangpu1005.updateStateByKey")
})

//5.4保存到mysql中
result.foreachRDD(rdd => {
  import spark.implicits._
  val url = "jdbc:mysql://superman-bigdata.com:3306/yangpu1005"
  val table = "updateStateByKey"
  val props = new Properties()
  props.put("user","root")
  props.put("password","123456")
  rdd.toDF("word","count")
    .write
    .mode("overwrite")
    .format("json")
    .jdbc(url,table,props)
})

//5.5保存到hbase表中
//方式一:将DStream转成RDD,然后使用RDD的foreachPartition写入hbase中
result
    .foreachRDD(rdd => {
      rdd.foreachPartition(iter => {
        //1.获取hbase的配置信息
        val config  = HBaseConfiguration.create()
        //2.获取hbase的表对象,首先在hbase中建表 create 'updateStateByKey','info'
        val table  = new HTable(config,"updateStateByKey")
        //3.构建put对象
        iter.foreach(t => {
          val word = t._1
          val count = t._2
          val put = new Put(Bytes.toBytes(word))
          put.add(Bytes.toBytes("info"),Bytes.toBytes("count"),Bytes.toBytes(count))
          table.put(put)
        })
        table.close()
      })
    })

//方法二:使用org.apache.spark.rdd.PairRDDFunctions中的saveAsNewAPIHadoopDataset写到hbase中

/* def saveAsNewAPIHadoopDataset(conf: Configuration): Unit = self.withScope {
SparkHadoopMapReduceWriter.write(
rdd = self,
hadoopConf = conf)
}*/
result
.foreachRDD(rdd => {
//1.设置配置参数
val sc = rdd.sparkContext
sc.hadoopConfiguration.set(“hbase.zookeeper.quorum”,“superman-bigdata.com:2181”) //zk的连接地址
sc.hadoopConfiguration.set(“hbase.rootdir”,“hdfs://superman-bigdata.com:9000/hbase”)
sc.hadoopConfiguration.set(“hbase.master”,“superman-bigdata.com:60000”)
//以上的我们这边可以不需要写,因为我们已经把hbase-site.xml文件放在项目中
sc.hadoopConfiguration.set(TableOutputFormat.OUTPUT_TABLE,“updateStateByKey”) //待输入的hbase的表
//2.构建job对象
val job = Job.getInstance(sc.hadoopConfiguration)
job.setOutputKeyClass(classOf[ImmutableBytesWritable])
job.setOutputValueClass(classOf[Result])
job.setOutputFormatClass(classOf[TableOutputFormat[ImmutableBytesWritable]])
rdd.saveAsNewAPIHadoopDataset(job.getConfiguration)
})

ssc.start()
ssc.awaitTermination()

}
}

===================================================================================================
4.Window 窗口 不是窗口函数!!!!
应用场景:计算最近一段时间的数据
例子:
批次间隔的时间是5s 计算的结果是每5s内接收到的数据结果
计算最近30s的数据结果,相当于计算最近6个批次的数据

窗口的执行过程:
需求:每隔20s,打印最近30s的数据
批次产生的间隔时间是10s
窗口的大小也就是最近的一段时间:30s
窗口的滑动时间也就是新窗口产生的间隔时间:20s
12:00:00 12:00:10 12:00:20 12:00:30 12:00:40 。。。。。
批次1 批次2 批次3 批次4 。。。
window1 window2 。。。
12:00:20
进行第一次的打印:window1 = 批次1 + 批次2

12:00:40
进行第二次的打印:window2 = window1 + 批次3 + 批次4 - 批次1 = 批次2 + 批次3 + 批次4

。。。。。
规则:
window = 上一个window + 新window产生的那段时间得到的新的批次 - 在上一个window中和当前window不重叠的部分

在使用Window的时候,代码中需不需要设置checkpoint机制???

代码:
package com.bigdata.SparkStreaming

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.ReceiverInputDStream

object SparkStreaming_Window {

def main(args: Array[String]): Unit = {
//1.构建上下文
val config = new SparkConf()
.setMaster(“local[*]”)
.setAppName(“SparkStreaming_Window”)

val sc = SparkContext.getOrCreate(config)
sc.setLogLevel("ERROR")

val ssc = new StreamingContext(sc,Seconds(10))

//checkpoint 检查点机制
val path = "hdfs://superman-bigdata.com:9000/yangpu1005/checkpoint"
ssc.checkpoint(path)

//2.读取数据形成DStream
val dstream: ReceiverInputDStream[String] =  ssc.socketTextStream("superman-bigdata.com",9999)

//3.使用window进行计算:需求:每隔20s,打印最近30s的数据

/* def reduceByKeyAndWindow(
reduceFunc: (V, V) => V, //聚合函数
invReduceFunc: (V, V) => V, //删除上一个window中不重叠的部分
windowDuration: Duration, 窗口的大小
slideDuration: Duration = self.slideDuration, 窗口滑动的时间
): DStream[(K, V)]*/
val result = dstream
.flatMap(line => line.split("\t"))
.map(word => (word,1))
.reduceByKeyAndWindow(
(a,b) => a + b, //上一个window + 新window产生的那段时间得到的新的批次
(c,d) => c - d ,//上一个window + 新window产生的那段时间得到的新的批次 - 在上一个window中和当前window不重叠的部分
Seconds(30), //windowDuration 窗口的大小 也就是最近一段时间
Seconds(20) //slideDuration 窗口的滑动时间 也就是新窗口产生的间隔时间
)

result.print()

ssc.start()
ssc.awaitTermination()

}
}

你可能感兴趣的:(老师笔记)