Spark Streaming基础

Spark Streaming
流式计算框架,类似于Storm

常用的实时计算引擎(流式计算)
1、Apache Storm:真正的流式计算

2、Spark Streaming :严格上来说,不是真正的流式计算(实时计算)
把连续的流式数据,当成不连续的RDD
本质:是一个离散计算(不连续)

3、Apache Flink:真正的流式计算。与Spark Streaming相反
把离散的数据,当成流式数据来处理

4、JStorm

一、Spark Streaming基础

1、什么是 Spark Streaming

Spark Streaming makes it easy to build scalable fault-tolerant streaming applications.
易于构建灵活的、高容错的流式系统

Spark Streaming是核心Spark API的扩展,可实现可扩展、高吞吐量、可容错的实时数据流处理。数据可以从诸如Kafka,Flume,Kinesis或TCP套接字等众多来源获取,并且可以使用由高级函数(如map,reduce,join和window)开发的复杂算法进行流数据处理。最后,处理后的数据可以被推送到文件系统,数据库和实时仪表板。而且,您还可以在数据流上应用Spark提供的机器学习和图处理算法

特点:
1、易用,已经集成到Spark中
2、容错性:底层RDD,RDD本身具有容错机制
3、支持多种语言:Java Scala Python

Spark Streaming将连续的数据流抽象为discretizedstream或DStream。在内部,DStream 由一个RDD序列表示

2、演示官方的Demo

往Spark Streaming中发送字符串,Spark 接收到以后,进行计数
使用消息服务器 netcat Linux自带
yum install nc.x86_64

nc -l 1234

注意:总核心数 大于等于2。一个核心用于接收数据,另一个用于处理数据

在netcat中写入数据 Spark Streaming可以取到

[root@hsiehchou121 spark-2.1.0-bin-hadoop2.7]# ./bin/run-example streaming.NetworkWordCount localhost 1234

3、开发自己的NetWorkWordCount程序

和Spark Core类似
代码

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
/**
* 开发自己的流式计算程序
*
* 知识点
* 1、创建一个StreamingContext对象 ----》核心:创建一个DStream
*
* 2、DStream的表现形式:就是一个RDD
*
* 3、使用DStream把连续的数据流变成不连续的RDD
*
* Spark Streaming 最核心的内容
*/
object MyNetworkWordCount {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyNetworkWordCount").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(3))
//创建DStream,从netcat服务器上接收数据
val lines = ssc.socketTextStream("192.168.116.121", 1234, StorageLevel.MEMORY_ONLY)
//lines中包含了netcat服务器发送过来的数据
//分词操作
val words = lines.flatMap(_.split(" "))
//计数
val wordCount = words.map((_,1)).reduceByKey(_+_)
//打印结果
wordCount.print()
//启动StreamingContext进行计算
ssc.start()
//等待任务结束
ssc.awaitTermination()
}
}

程序中的几点说明
appName参数是应用程序在集群UI上显示的名称

master是Spark,Mesos或YARN集群的URL,或者一个特殊的“local [*]”字符串来让程序以本地模式运行

当在集群上运行程序时,不需要在程序中硬编码master参数,而是使用spark-submit提交应用程序并将master的URL以脚本参数的形式传入。但是,对于本地测试和单元测试,您可以通过“local[*]”来运行Spark Streaming程序(请确保本地系统中的cpu核心数够用)

StreamingContext会内在的创建一个SparkContext的实例(所有Spark功能的起始点),你可以通过ssc.sparkContext访问到这个实例

批处理的时间窗口长度必须根据应用程序的延迟要求和可用的集群资源进行设置

请务必记住以下几点
一旦一个StreamingContext开始运作,就不能设置或添加新的流计算

一旦一个上下文被停止,它将无法重新启动

同一时刻,一个JVM中只能有一个StreamingContext处于活动状态

StreamingContext上的stop()方法也会停止SparkContext。 要仅停止StreamingContext(保持SparkContext活跃),请将stop() 方法的可选参数stopSparkContext设置为false

只要前一个StreamingContext在下一个StreamingContext被创建之前停止(不停止SparkContext),SparkContext就可以被重用来创建多个StreamingContext

问题:Hello Hello
Hello World

现在现象:(Hello,2)
(Hello , 1) (World , 1)

能不能累加起来?保存记录下以前的状态?
能,能
通过Spark Streaming提供的算子来实现

二、高级特性

1、什么是DStream?离散流

把连续的数据变成不连续的RDD
因为DStream的特性,导致Spark Streaming不是真正的流式计算

离散流(DStreams):Discretized Streams
DiscretizedStream或DStream 是Spark Streaming对流式数据的基本抽象。它表示连续的数据流,这些连续的数据流可以是从数据源接收的输入数据流,也可以是通过对输入数据流执行转换操作而生成的经处理的数据流。在内部,DStream由一系列连续的RDD表示

举例分析:
在之前的MyNetworkWordCount 的例子中,我们将一行行文本组成的流转换为单词流,具体做法为:将flatMap操作应用于名为lines的 DStream中的每个RDD上,以生成words DStream的RDD

DStream中的转换操作(transformation)

Transformation Meaning
map(func) 利用函数func处理DStreamd的每个元素,返回一个新的DStream
flatMap(func) 于map相似,但是每个输入项可被映射为0个或者多个输出项
filter(func) 返回一个新的DStream,它仅仅包含源DStream中满足函数func的项
repartition(numPartitions) 通过创建更多或者更少的partition改变这个DStream的并行级别(level of parallelism)
union(otherStream) 返回一个新的DStream,它包含源DStream和otherStream的联合元素
count() 通过计算源DStream中每个RDD的元素数量,返回一个包含单元素(single-element)RDDs的新DStream
reduce(func) 利用函数func聚焦源DStream中每个RDD的元素,返回一个包含单元素(single-element)RDDs的新DStream,函数应该是相关联的,以使计算可以并行化
countByValue() 这个算子应用于元素类型为K的DStream上,返回一个(K,long)对的新DStream,每个键的值实在原DStream的每个RDD中的频率
reduceByKey(func,[numTasks]) 当在一个由(K,V)对组成的DStream上调用这个算子,返回一个新的由(K,V)对组成的DStream,每一个key的值均由给定的reduce函数聚集起来。注意:在默认情况下,这个算子利用了Spark默认的并发任务数去分组,你可以用numTasks参数设置不同的任务数
join(otherStream,[numTasks]) 当应用于两个DStream(一个包含(K,V)对,一个包含(K,W)对),返回一个包含(K,(V,W))对的新DStream
cogroup(otherStream,[numTasks]) 当应用于两个DStream(一个包含(K,V)对,一个包含(K,W)对),返回一个包含(K,Seq[v],Seq[W])的元组
transform(func) 通过对源DStream的每个RDD应用RDD-to-RDD函数,创建一个新的DStream,这个可以在DStream中的任何RDD操作中使用
updateStateByKey(func) 利用给定的函数更新DStream的状态,返回一个新”state”的DStream

2、重点算子讲解

(1)updateStateByKey(func)
默认情况下,Spark Streaming不记录之前的状态,每次发数据,都会从0开始
现在使用本算子,实现累加操作

操作允许不断用新信息更新它的同时保持任意状态
定义状态-状态可以是任何的数据类型
定义状态更新函数-怎样利用更新前的状态和从输入流里面获取的新值更新状态

重写MyNetworkWordCount程序,累计每个单词出现的频率(注意:累计)

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
import javax.swing.text.DefaultEditorKit.PreviousWordAction
/**
* 实现累加操作
*/
object MyTotalNetworkWordCount {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyTotalNetworkWordCount ").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(3))
//设置检查点目录,保存之前的状态信息
ssc.checkpoint("hdfs://hsiehchou121:9000/tmp_files/chkp")
//创建DStream 从netcat服务器上接收数据
val lines = ssc.socketTextStream("192.168.116.121", 1234, StorageLevel.MEMORY_ONLY)
val words = lines.flatMap(_.split(" "))
val wordPair = words.map((_,1))
/**
* 定义一个值函数,进行累加运算
* 1、当前值是多少(参数1)
* 2、之前的结果是多少(参数2)
*/
val addFunc = (currentValues:Seq[Int], previousValues:Option[Int]) =>{
//进行累加运算
//1、把当前的序列进行累加
val currentTotal = currentValues.sum
//2、在之前的值上再累加
Some(currentTotal + previousValues.getOrElse(0))
}
//进行累加运算
val total = wordPair.updateStateByKey(addFunc)
total.print()
ssc.start()
ssc.awaitTermination()
}
}

我在执行过程中遇到访问权限问题
解决如下:
在hadoop的etc/hadoop/下的hdfs-site.xml中增加如下内容即可

<property>
<name>dfs.permissions</name>
<value>false</value>
</property>
<property>
<name>dfs.safemode.threshold.pct</name>
<value>0f</value>
</property>

(2)transform(func)
通过RDD-to-RDD函数作用于源DStream中的各个RDD,可以是任意的RDD操作,从而返回一个新的RDD

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
/**
* 开发自己的流式计算程序
*
* 知识点
* 1、创建一个StreamingContext对象 ----》核心:创建一个DStream
*
* 2、DStream的表现形式:就是一个RDD
*
* 3、使用DStream把连续的数据流变成不连续的RDD
*
* Spark Streaming 最核心的内容
*/
object MyNetworkWordCount {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyNetworkWordCount ").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(3))
//创建DStream,从netcat服务器上接收数据
val lines = ssc.socketTextStream("192.168.116.121", 1234, StorageLevel.MEMORY_ONLY)
//lines中包含了netcat服务器发送过来的数据
//分词操作
val words = lines.flatMap(_.split(" "))
//计数
val wordPair = words.transform(x => x.map(x => (x, 1)))
//打印结果
wordPair.print()
//启动StreamingContext进行计算
ssc.start()
//等待任务结束
ssc.awaitTermination()
}
}

3、窗口操作

窗口:对落在窗口内的数据进行处理,也是一个DStream,RDD

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
/**
* 窗口操作:
* 需求:每10秒钟,把过去30秒的数据读取进来
*/
object MyNetworkWordCountByWindow {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyNetworkWordCountByWindow").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(1))
//创建DStream 从netcat服务器上接收数据
val lines = ssc.socketTextStream("192.168.116.121", 1234, StorageLevel.MEMORY_ONLY)
//lines中包含了netcat服务器发送过来的数据
//分词操作 给每个单词记一次数
val words = lines.flatMap(_.split(" ")).map((_,1))
/*
* reduceByKeyAndWindow 函数的三个参数
* 1、需要进行什么操作
* 2、窗口的大小30秒
* 3、窗口滑动的距离10秒
*/
val result = words.reduceByKeyAndWindow((x:Int,y:Int)=>(x+y),Seconds(30),Seconds(10))
result.print()
ssc.start()
ssc.awaitTermination()
/*
* The slide duration of windowed DStream (10000 ms) must be a multiple of the slide
* duration of parent DStream (3000 ms)
*
* 注意:窗口滑动距离必须是采样时间的整数倍
*/
}
}

举例:每10秒钟把过去30秒的数据采集过来
注意:先启动nc 再启动程序 local[2]

4、集成Spark SQL: 使用SQL语句来处理流式数据

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
import org.apache.spark.sql.SparkSession
/**
* 集成Spark SQL : 在Spark Streaming中使用SQL语句
*/
object MyNetworkWordCountWithSQL {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyNetworkWordCountByWindow").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(10))
//创建DStream 从netcat服务器上接收数据
val lines = ssc.socketTextStream("192.168.116.121", 1234, StorageLevel.MEMORY_ONLY)
//进行单词计数
val words = lines.flatMap(_.split(" "))
//集成Spark SQL 使用SQL语句实现WordCount
words.foreachRDD(rdd =>{
//创建一个Spark Session对象
//通过ssc.sparkContext.getConf 直接获取此session的conf
val spark = SparkSession.builder().config(ssc.sparkContext.getConf).getOrCreate()
//把RDD转换成DataFrame 需要用到隐式转换
import spark.implicits._
val df1 = rdd.toDF("word")//表df1 只有一个列名 名字叫word
//创建视图
df1.createOrReplaceTempView("words")
//执行SQL 通过SQL实现wordcount
spark.sql("select word,count(1) from words group by word").show
}
)
ssc.start()
ssc.awaitTermination()
}
}

5、缓存和持久化:和RDD一样

与RDD类似,DStreams还允许开发人员将流数据保留在内存中。也就是说,在DStream上调用persist() 方法会自动将该DStream的每个RDD保留在内存中。如果DStream中的数据将被多次计算(例如,相同数据上执行多个操作),这个操作就会很有用。对于基于窗口的操作,如reduceByWindow和reduceByKeyAndWindow以及基于状态的操作,如updateStateByKey,数据会默认进行持久化。 因此,基于窗口的操作生成的DStream会自动保存在内存中,而不需要开发人员调用persist()

对于通过网络接收数据(例如Kafka,Flume,sockets等)的输入流,默认持久化级别被设置为将数据复制到两个节点进行容错

注意,与RDD不同,DStreams的默认持久化级别将数据序列化保存在内存中

6、支持检查点:和RDD一样

流数据处理程序通常都是全天候运行,因此必须对应用中逻辑无关的故障(例如,系统故障,JVM崩溃等)具有弹性。为了实现这一特性,Spark Streaming需要checkpoint足够的信息到容错存储系统,以便可以从故障中恢复

① 一般会对两种类型的数据使用检查点:
1)元数据检查点(Metadatacheckpointing) - 将定义流计算的信息保存到容错存储中(如HDFS)。这用于从运行streaming程序的driver程序的节点的故障中恢复。元数据包括以下几种:
 配置(Configuration) - 用于创建streaming应用程序的配置信息

 DStream操作(DStream operations) - 定义streaming应用程序的DStream操作集合

 不完整的batch(Incomplete batches) - jobs还在队列中但尚未完成的batch

2)数据检查点(Datacheckpointing) - 将生成的RDD保存到可靠的存储层。对于一些需要将多个批次之间的数据进行组合的stateful变换操作,设置数据检查点是必需的。在这些转换操作中,当前生成的RDD依赖于先前批次的RDD,这导致依赖链的长度随时间而不断增加,由此也会导致基于血统机制的恢复时间无限增加。为了避免这种情况,stateful转换的中间RDD将定期设置检查点并保存到到可靠的存储层(例如HDFS)以切断依赖关系链

总而言之,元数据检查点主要用于从driver程序故障中恢复,而数据或RDD检查点在任何使用stateful转换时是必须要有的

② 何时启用检查点:
对于具有以下任一要求的应用程序,必须启用检查点:
1)使用状态转:如果在应用程序中使用updateStateByKey或reduceByKeyAndWindow(具有逆函数),则必须提供检查点目录以允许定期保存RDD检查点
2)从运行应用程序的driver程序的故障中恢复:元数据检查点用于使用进度信息进行恢复

③ 如何配置检查点:
可以通过在一些可容错、高可靠的文件系统(例如,HDFS,S3等)中设置保存检查点信息的目录来启用检查点。这是通过使用streamingContext.checkpoint(checkpointDirectory)完成的。设置检查点后,您就可以使用上述的有状态转换操作。此外,如果要使应用程序从驱动程序故障中恢复,您应该重写streaming应用程序以使程序具有以下行为:
1)当程序第一次启动时,它将创建一个新的StreamingContext,设置好所有流数据源,然后调用start()方法。
2)当程序在失败后重新启动时,它将从checkpoint目录中的检查点数据重新创建一个StreamingContext。
使用StreamingContext.getOrCreate可以简化此行为

④ 改写之前的WordCount程序,使得每次计算的结果和状态都保存到检查点目录下
hdfs dfs -ls /spark_checkpoint

三、数据源

Spark Streaming是一个流式计算引擎,就需要从外部数据源来接收数据

1、基本的数据源

文件流:监控文件系统的变化,如果文件有增加,读取文件中的内容

希望Spark Streaming监控一个文件夹,如果有变化,则把变化采集过来

此功能为:修改文件里面的内容,并修改文件名,才能检测到,单修改一个是不起作用的

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
/**
* 测试文件流
*/
object FileStreaming {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyNetworkWordCountByWindow").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(1))
//直接监控某个目录,如果有新文件产生,就读取出来
val lines = ssc.textFileStream("H:\\other\\test_file_stream")
lines.print()
ssc.start()
ssc.awaitTermination()
}
}

RDD队列流:可以从队列中获取数据(不常用)

package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.storage.StorageLevel
import org.apache.log4j.Logger
import org.apache.log4j.Level
import org.apache.spark.sql.SparkSession
import org.apache.spark.rdd.RDD
import scala.collection.mutable.Queue
/**
* RDD队列流
*/
object RDDQueueStream {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("RDDQueueStream").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(3))
//需要先创建一个队列RDD[Int]
val rddQueue = new Queue[RDD[Int]]()
//往队列里面添加数据 ----> 创建数据源
for(i <- 1 to 3){
rddQueue += ssc.sparkContext.makeRDD(1 to 10)
//为了便于观察
Thread.sleep(1000)
}
//从队列中接收数据,创建DStream
val inputDStream = ssc.queueStream(rddQueue)
//处理数据
val result = inputDStream.map(x => (x, x*2))
result.print()
ssc.start()
ssc.awaitTermination()
}
}

套接字流:socketTextStream

2、高级数据源

(1)Flume
Spark SQL 对接flume有多种方式:
push方式:flume将数据推送给Spark Streaming
flume/myagent/a4.conf

# bin/flume-ng agent -n a4 -f myagent/a4.conf -c conf -Dflume.root.logger=INFO.console
# 定义agent名,source、channel、sink的名称
a4.sources = r1
a4.channels = c1
a4.sinks = k1
# 具体定义source
a4.sources.r1.type = spooldir
a4.sources.r1.spoolDir = /root/hd/tmp_files/logs
# 具体定义channel
a4.channels.c1.type = memory
a4.channels.c1.capacity = 10000
a4.channels.c1.transactionCapacity = 100
# 具体定义sink
a4.sinks = k1
a4.sinks.k1.type = avro
a4.sinks.k1.channel = c1
a4.sinks.k1.hostname = 192.168.116.1
a4.sinks.k1.port = 1234
# 组装 source、channel、sink
a4.sources.r1.channels = c1
a4.sinks.k1.channel = c1
package day5
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.log4j.Logger
import org.apache.log4j.Level
import org.apache.spark.streaming.flume.FlumeUtils
object MyFlumeStream {
def main(args: Array[String]): Unit = {
//减少Info日志的打印
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//创建一个StreamingContext对象
//local[2]表示开启了两个线程
val conf = new SparkConf().setAppName("MyFlumeStream").setMaster("local[2]")
//Seconds(3)表示采样时间间隔
val ssc = new StreamingContext(conf, Seconds(3))
//对象flume
//创建一个flumeEvent 从flume中接收push来的数据,也是一个DStream
//flume将数据push到"192.168.116.1",1234 Spark Streaming在这里监听
val flumeEventDStream = FlumeUtils.createStream(ssc, "192.168.116.1", 8888)
//将FlumeEvent中的事件转换成字符串
val lineDStream = flumeEventDStream.map(e => {
new String(e.event.getBody.array)
})
//输出结果
lineDStream.print()
ssc.start()
ssc.awaitTermination()
}
}

custom sink 模式:比第一种有更好的健壮性和容错性。使用这种方式,flume配置一个sink
a1.conf

#bin/flume-ng agent -n a1 -f myagent/a1.conf -c conf -Dflume.root.logger=INFO,console
a1.channels = c1
a1.sinks = k1
a1.sources = r1
a1.sources.r1.type = spooldir
a1.sources.r1.spoolDir = /root/hd/tmp_files/logs
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 100000
a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
a1.sinks.k1.channel = c1
a1.sinks.k1.hostname = 192.168.116.121
a1.sinks.k1.port = 1234
#组装source、channel、sink
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

使用官方提供的spark sink组件

需要把 spark-streaming-flume-sink_2.10-2.1.0.jar 拷贝到flume lib下
需要把 spark-streaming-flume-sink_2.10-2.1.0.jar 拷贝到IDE的lib下添加到build path中

(2)Kafka
在讲Kafka时,举例

四、性能优化的参数

性能优化:
spark submit的时候,程序报OOM错误
程序跑的很慢

1、减少批数据的执行时间

在Spark中有几个优化可以减少批处理的时间:
① 数据接收的并行水平
通过网络(如kafka,flume,socket等)接收数据需要这些数据反序列化并被保存到Spark中。如果数据接收成为系统的瓶颈,就要考虑并行地接收数据。注意,每个输入DStream创建一个receiver(运行在worker机器上)接收单个数据流。创建多个输入DStream并配置它们可以从源中接收不同分区的数据流,从而实现多数据流接收。例如,接收两个topic数据的单个输入DStream可以被切分为两个kafka输入流,每个接收一个topic。这将在两个worker上运行两个receiver,因此允许数据并行接收,提高整体的吞吐量。多个DStream可以被合并生成单个DStream,这样运用在单个输入DStream的transformation操作可以运用在合并的DStream上

② 数据处理的并行水平
如果运行在计算stage上的并发任务数不足够大,就不会充分利用集群的资源。默认的并发任务数通过配置属性来确定spark.default.parallelism

③ 数据序列化
可以通过改变序列化格式来减少数据序列化的开销。在流式传输的情况下,有两种类型的数据会被序列化:
1)输入数据
2)由流操作生成的持久RDD
在上述两种情况下,使用Kryo序列化格式可以减少CPU和内存开销

2、设置正确的批容量

为了Spark Streaming应用程序能够在集群中稳定运行,系统应该能够以足够的速度处理接收的数据(即处理速度应该大于或等于接收数据的速度)。这可以通过流的网络UI观察得到。批处理时间应该小于批间隔时间

根据流计算的性质,批间隔时间可能显著的影响数据处理速率,这个速率可以通过应用程序维持。可以考虑WordCountNetwork这个例子,对于一个特定的数据处理速率,系统可能可以每2秒打印一次单词计数(批间隔时间为2秒),但无法每500毫秒打印一次单词计数。所以,为了在生产环境中维持期望的数据处理速率,就应该设置合适的批间隔时间(即批数据的容量)

找出正确的批容量的一个好的办法是用一个保守的批间隔时间(5-10,秒)和低数据速率来测试你的应用程序

3、内存调优

在这一节,我们重点介绍几个强烈推荐的自定义选项,它们可以减少Spark Streaming应用程序垃圾回收的相关暂停,获得更稳定的批处理时间

1)Default persistence level of DStreams:和RDDs不同的是,默认的持久化级别是序列化数据到内存中(DStream是StorageLevel.MEMORY_ONLY_SER,RDD是StorageLevel.MEMORY_ONLY)。即使保存数据为序列化形态会增加序列化/反序列化的开销,但是可以明显的减少垃圾回收的暂停

2)Clearing persistent RDDs:默认情况下,通过Spark内置策略(LUR),Spark Streaming生成的持久化RDD将会从内存中清理掉。如果spark.cleaner.ttl已经设置了,比这个时间存在更老的持久化RDD将会被定时的清理掉。正如前面提到的那样,这个值需要根据Spark Streaming应用程序的操作小心设置。然而,可以设置配置选项spark.streaming.unpersist为true来更智能的去持久化(unpersist)RDD。这个配置使系统找出那些不需要经常保有的RDD,然后去持久化它们。这可以减少Spark RDD的内存使用,也可能改善垃圾回收的行为

3)Concurrent garbage collector:使用并发的标记-清除垃圾回收可以进一步减少垃圾回收的暂停时间。尽管并发的垃圾回收会减少系统的整体吞吐量,但是仍然推荐使用它以获得更稳定的批处理时间

方法:调整spark参数
conf.set…

posted @ 2019-04-19 21:38  hsiehchou  阅读(152)  评论(0编辑  收藏  举报