07-Spark Streaming
1.目录概述
-
掌握spark Streaming的原理和架构
-
掌握DStream的相关操作
-
实现spark Streaming与flume整合
-
实现spark Streaming与kafaka整合
2.spark Streaming介绍
2.1.什么是spark Streaming
spark Streaming类似于Apache Storm,用于流式数据处理。根据官方文档介绍,spark Streaming有高吞吐量和容错能力强等特点。
spark Streaming支持多数据源。例如:Kafka、Flume、Twitter、ZeroMQ和简单的TCP套接字等。数据输入后可以用spark的高度抽象操作如:map、reduce、join、window等进行运算。结果可以存储保存在多个地方。如HDFS,数据库等。
2.2.为什么要学习spark Streaming
-
易用
可以像编写离线批处理一样去编写流式程序,支持java/scala/python语言。
-
容错
spark Streaming在没有额外代码和配置的情况下可以恢复丢失的工作。
-
与spark体系容易整合
都在spark体系下,流式处理、批处理、交互式查询结合容易。
2.3.spark Streaming 与storm对比
详细对比:
对比事项 | storm | spark Streaming |
---|---|---|
实时性 | 纯实时,有一条数据,处理一条数据 | 准实时,把一个时间段内的数据收集依赖,再处理 |
延迟度 | 毫秒级 | 秒级 |
吞吐量 | 相对低 | 高 |
事务机制 | 支持完善 | 支持,不够完善 |
健壮性/容错性 | 非常强 | 一般 |
应用场景选择:
1.对于像金融系统、股票系统,要求纯实时的金融交易和分析,不能接受1秒以上的延迟,选择storm更好
2.对于可靠性事务机制要求高,即要求数据处理完全精准,一条数据都不能多,也不能少,选择storm更好
3.对于系统实时要求没有那么高,可以接受秒级别的延迟,选择spark Streaming会更好,因为spark Streaming有更高的吞吐量(单位时间处理的数据量更多)
4.对于系统除了实时性因素,如果还需要考虑离线批处理、交互式查询等业务功能,选择spark Streaming会更好,因为spark Streaming更容易和spark体系中的其他模块整合(spark Core、spark sql)
3.spark Streaming原理
3.1.spark Streaming原理
spark Streaming 是基于spark的流式【批处理】引擎,基本原理是把输入数据以某一时间间隔进行批量处理。当批处理间隔缩短到秒级时,可以用于处理实时数据流。
细节:spark Streaming不是实时处理,而是准实时处理,有秒级别的延迟。
3.2.spark Streaming计算流程
1.输入数据流入spark Streaming
2.spark Streaming将输入数据按照batch size分成一段一段的数据,每一段数据都转换成RDD
3.将spark Streaming中对DStream的Transformation操作,转换成对RDD的Transformation操作。将RDD经过操作变成中间结果保存在内存中。根据业务需求可以对中间计算结果叠加或者存储到外部设备
3.3.spark Streaming容错性
对于流式计算来说,容错性至关重要。首先我们要明确spark中RDD的容错机制。每一个RDD都是一个不可变的分布式可重算的数据集,其记录着确定性的操作继承关系(lineage),只要输入数据是可容错的,那么任意一个RDD的分区(Partition)出错或不可用,都可以利用原始输入数据通过转换操作而重新算出的。
3.4.spark Streaming实时性
- spark Streaming的最小batch size 选取在(0.5~2)秒之间,属于准实时。适合于可以接受秒级别延迟的业务需求场景
- 对于实时性要求非常高,比如金融系统、股票分析系统,storm会是更好的选择(storm的延迟在100ms左右)
4.DStream
4.1.什么是DStream
DStream(Discretized Stream)是spark Streaming的基础抽象。代表持续性的数据流和经过各种spark算子操作后的结果数据流。内部实现上,DStream表示为一系列连续的RDD。每个RDD含有一段时间间隔内的数据,如下图:
数据操作按照RDD为单位来进行:
spark Streaming使用数据源产生的数据流创建DStream,或者在已有的DStream上通过算子操作来创建新的DStream。它的工作流程像下面的图所示一样,接受到实时数据后,给数据分批次,然后传给spark Engine处理最后生成该批次的结果。
5.DStream相关操作
5.1.Transformations on DStream
Transformation | Meaning |
---|---|
map(func) | 对DStream中的各个元素进行func函数操作,然后返回一个新的DStream |
flatMap(func) | 与map方法类似,只不过各个输入项可以被输出为零个或多个输出项 |
filter(func) | 过滤出所有函数func返回值为true的DStream元素并返回一个新的DStream |
repartition(numPartitions) | 增加或减少DStream中的分区数,从而改变DStream的并行度 |
union(otherStream) | 将源DStream和输入参数为otherDStream的元素合并,并返回一个新的DStream |
count() | 通过对DStream中的各个RDD中的元素进行计数,然后返回只有一个元素的RDD构成的DStream |
reduce(func) | 对源DStream中的各个RDD中的元素利用func进行聚合操作,然后返回只有一个元素的RDD构成的新的DStream |
countByValue() | 对于元素类型为K的DStream,返回一个元素为(K,Long)键值对形式的新的DStream,Long对应的值为源DStream中各个RDD的key出现的次数 |
reduceByKey(func, [numTasks]) | 利用func函数对源DStream中的key进行聚合操作,然后返回新的(K,V)对构成的DStream |
join(otherStream, [numTasks]) | 输入为(K,V)、(K,W)类型的DStream,返回一个新的(K,(V,W))类型的DStream |
cogroup(otherStream, [numTasks]) | 输入为(K,V)、(K,W)类型的DStream,返回一个新的 (K, Seq[V], Seq[W]) 元组类型的DStream |
transform(func) | 通过RDD-to-RDD函数作用于DStream中的各个RDD,可以是任意的RDD操作,从而返回一个新的RDD |
updateStateByKey(func) | 根据key的之前状态值和key的新值,对key进行更新,返回一个新状态的DStream |
特殊的Transformations操作:
-
UpdateStateByKey Operation
UpdateStateByKey用于记录历史记录,保存上次的状态
-
**Window Operations(**开窗函数)
滑动窗口转换操作:
1.红色的矩形就是一个窗口,窗口框住的是一段时间内的数据流
2.这里面每一个time都是时间单元,在官方的例子中,每隔window length是3 time unit, 而且每隔2个单位时间,窗口会slide一次。
**细节:**基于窗口的操作,需要指定2个参数。
- window length:窗口长度,表示一段时间内数据的容器
- slide interval:滑动时间间隔,表示每隔多久计算一次
5.2.Output Operations on DStreams
Output Operations可以将DStream的数据输出到外部的数据库或文件系统。当某个Output Operations被调用时(与RDD的Action相同),spark streaming程序才会开始真正的计算过程。
Output Operation | Meaning |
---|---|
print() | 打印到控制台 |
saveAsTextFiles(prefix, [suffix]) | 保存流的内容为文本文件,文件名为: prefix-TIME_IN_MS[.suffix] |
saveAsObjectFiles(prefix, [suffix]) | 保存流的内容为SequenceFile,文件名为: prefix-TIME_IN_MS[.suffix] |
saveAsHadoopFiles(prefix, [suffix]) | 保存流的内容为hadoop文件,文件名为: prefix-TIME_IN_MS[.suffix] |
foreachRDD(func) | 对Dstream里面的每个RDD执行func |
6.DStream操作实战
6.1.接收socket数据,实现单词计数WordCount
6.1.1.架构图
6.1.2.实现流程
6.1.2.1.安装netcat
说明:netcat是一个通过tcp/udp协议,在网络中进行数据读写的工具,被称为“瑞士军刀”。利用netcat可以将网络中一端的数据,完整发送到另外一台主机终端显示或存储。可以用于文件传输、及时通信等。
yum install -y nc
netcat工具简单应用案例:
#扫描端口:
nc -v www.baidu.com 80
#及时通信,将hadoop01作为server主机,通信端口设置为10000
nc -l 10000
#将hadoop02作为client主机,连接到hadoop01的10000端口
nc 192.168.80.21 10000
图一:hadoop01
图二:hadoop02
6.1.2.2.通过netcat向指定端口发送数据
#node01
nc -lk 10000
6.1.2.3.开发spark Streaming案例程序
- 创建项目
-
导入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.liny</groupId> <artifactId>spark-teach-day07-01project</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <scala.version>2.11.8</scala.version> <spark.version>2.0.2</spark.version> </properties> <dependencies> <!--scala依赖--> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <!--spark依赖--> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>${spark.version}</version> </dependency> </dependencies> <build> <plugins> <!--scala编译插件--> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <version>2.15.2</version> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> <!-- java 编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
-
编写案例代码
package cn.liny.stream
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
/**
* 学习spark Streaming接收Socket数据,实现单词计数
*/
object WordCountByStreaming {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf().setAppName("WordCountByStreaming").setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
// 4.注册监听Ip地址和端口,接收数据
/**
* 参数说明:
* 参数一:服务器主机的ip地址
* 参数二:服务端口
*/
val dataLines: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.53.100",10000)
// 5.切分每一行记录
val wordsDS: DStream[String] = dataLines.flatMap(_.split(" "))
// 6.每个单词计数为1
val wordAndOneDS: DStream[(String, Int)] = wordsDS.map((_,1))
// 7.聚合操作
val resultDS: DStream[(String, Int)] = wordAndOneDS.reduceByKey(_+_)
// 8.通过Output Operations操作打印数据
resultDS.print()
// 9.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
}
6.1.3.执行查看效果
图一:
图二:
6.1.4.代码步骤小结
1.创建SparkConf对象
– 细节:在spark Streaming中,执行任务至少需要2core的cpu内核资源。分别用于接收数据,和处理数据。
2.创建SparkContext对象
3.创建StreamingContext对象。需要提供两个参数(参数一:SparkContext对象,参数二:每个批次的间隔时间)
4.注册监听Ip地址和端口,接收数据。需要提供两个参数(参数一:服务器主机的ip地址,参数二:服务端口)
5.根据业务需求,将数据进行Transformations 操作
6.根据业务需求,将处理好的结果数据进行Output Operations操作
7.开启流式计算,并且要一直阻塞运行
6.2.接收socket数据,实现所有批次单词计数结果累加
在第一个案例中存在一个问题:每个批次的单词次数都被正确的统计出来,但是结果不能累加!
如果将所有批次的结果数据进行累加,使用**updateStateByKey(func)**来实现。
6.2.1.编写案例代码
package cn.liny.stream
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
/**
* 学习spark Streaming接收Socket数据,实现单词计数(将所有批次结果进行累加)
*/
object WordCountByStreamingSum {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf().setAppName("WordCountByStreaming").setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
/**
* 细节:实现累加,需要设置checkpoint目录
*/
ssc.checkpoint("./wordsck")
// 4.注册监听Ip地址和端口,接收数据
/**
* 参数说明:
* 参数一:服务器主机的ip地址
* 参数二:服务端口
*/
val dataLines: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.53.100",10000)
// 5.切分每一行记录
val wordsDS: DStream[String] = dataLines.flatMap(_.split(" "))
// 6.每个单词计数为1
val wordAndOneDS: DStream[(String, Int)] = wordsDS.map((_,1))
// 7.实现所有批次结果数据累加
/**
* 实现方法:updateStateByKey
* 参数:myFunc
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.updateStateByKey(myFunc)
// 8.通过Output Operations操作打印数据
resultDS.print()
// 9.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
/**
* 定义实现批次结果数据累加函数
* 参数说明:
* currentValues:当前批次每个单词出现的所有1。比如:(spark,1)(spark,1)
* historyValues:之前所有批次中每个单词出现的总次数。比如:(spark,50)
*
* 返回值:
* 当前批次,与之前所有批次,每个单词出现的总次数(累加结果)
*/
def myFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int]={
// 将当前批次每个单词出现的所有1求和
val currentSum: Int = currentValues.sum
// 将当前批次单词求和结果,与之前批次总结果进行累加
val resultSum: Int = currentSum+historyValues.getOrElse(0)
Some(resultSum)
}
}
6.2.2.测试
图一:
图二:
6.2.3.小结
1.设置checkpoint目录:
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
/**
* 细节:实现累加,需要设置checkpoint目录
*/
ssc.checkpoint("./wordsck")
2.通过updateStateByKey实现批次结果累加:
// 7.实现所有批次结果数据累加
/**
* 实现方法:updateStateByKey
* 参数:myFunc
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.updateStateByKey(myFunc)
3.updateStateByKey方法中函数定义:
/**
* 定义实现批次结果数据累加函数
* 参数说明:
* currentValues:当前批次每个单词出现的所有1。比如:(spark,1)(spark,1)
* historyValues:之前所有批次中每个单词出现的总次数。比如:(spark,50)
*
* 返回值:
* 当前批次,与之前所有批次,每个单词出现的总次数(累加结果)
*/
def myFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int]={
// 将当前批次每个单词出现的所有1求和
val currentSum: Int = currentValues.sum
// 将当前批次单词求和结果,与之前批次总结果进行累加
val resultSum: Int = currentSum+historyValues.getOrElse(0)
Some(resultSum)
}
6.3.开窗函数reduceByKeyAndWindow,实现单词计数
说明:开窗函数可以用于计算一段时间内的数据。
6.3.1.编写案例代码
package cn.liny.stream
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
/**
* 学习spark Streaming接收Socket数据,开窗函数:统计一定时间内单词出现的次数
*/
object WordCountByStreamingWin {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf().setAppName("WordCountByStreaming").setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
// 设置checkpoint目录
ssc.checkpoint("./winck")
// 4.注册监听Ip地址和端口,接收数据
/**
* 参数说明:
* 参数一:服务器主机的ip地址
* 参数二:服务端口
*/
val dataLines: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.53.100",10000)
// 5.切分每一行记录
val wordsDS: DStream[String] = dataLines.flatMap(_.split(" "))
// 6.每个单词计数为1
val wordAndOneDS: DStream[(String, Int)] = wordsDS.map((_,1))
// 7.开窗函数(reduceByKeyAndWindow):统计一段时间内单词出现的次数
/**
* 开窗函数参数说明:
* reduceFunc:业务操作函数
* windowDuration:窗口长度,表示window框住的时间长度。如本例5秒切分一次RDD,框10秒,就会保留最近2次切分的RDD
* slideDuration:窗口滑动时间间隔,表示window滑动的时间长度,即每隔多久执行计算
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.reduceByKeyAndWindow(
(x:Int,y:Int)=>x+y,
Seconds(10),
Seconds(10)
)
// 8.通过Output Operations操作打印数据
resultDS.print()
// 9.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
}
6.3.2.小结
1.开窗函数reduceByKeyAndWindow说明:
// 7.开窗函数(reduceByKeyAndWindow):统计一段时间内单词出现的次数
/**
* 开窗函数参数说明:
* reduceFunc:业务操作函数
* windowDuration:窗口长度,表示window框住的时间长度。如本例5秒切分一次RDD,框10秒,就会保留最近2次切分的RDD
* slideDuration:窗口滑动时间间隔,表示window滑动的时间长度,即每隔多久执行计算
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.reduceByKeyAndWindow(
(x:Int,y:Int)=>x+y,
Seconds(10),
Seconds(10)
)
2.窗口长度windowDuration,如果大于窗口滑动时间slideDuration,会发生重复计算
3.窗口长度windowDuration,如果小于窗口滑动时间slideDuration,会丢失数据
4.在实际应用中,窗口长度和窗口滑动时间,都指定为批次的间隔时间的整数倍
6.4.开窗函数统计一定时间内的热门词汇
6.4.1.需求
sparkStreaming每隔5s计算一次当前在窗口大小为10s内的数据,然后将单词出现次数最多的前3位进行输出打印。
6.4.2.编写案例代码
package cn.liny.stream
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
/**
* 学习spark Streaming接收Socket数据,开窗函数:
* 每隔5s计算一次当前在窗口大小为10s内的数据,然后将单词出现次数最多的前3位进行输出打印
*/
object WordCountByStreamingWinHot {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf().setAppName("WordCountByStreamingWinHot").setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
// 设置checkpoint目录
ssc.checkpoint("./winck")
// 4.注册监听Ip地址和端口,接收数据
/**
* 参数说明:
* 参数一:服务器主机的ip地址
* 参数二:服务端口
*/
val dataLines: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.53.100",10000)
// 5.切分每一行记录
val wordsDS: DStream[String] = dataLines.flatMap(_.split(" "))
// 6.每个单词计数为1
val wordAndOneDS: DStream[(String, Int)] = wordsDS.map((_,1))
// 7.开窗函数(reduceByKeyAndWindow):统计一段时间内单词出现的次数
/**
* 开窗函数参数说明:
* reduceFunc:业务操作函数
* windowDuration:窗口长度,表示window框住的时间长度。如本例5秒切分一次RDD,框10秒,就会保留最近2次切分的RDD
* slideDuration:窗口滑动时间间隔,表示window滑动的时间长度,即每隔多久执行计算
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.reduceByKeyAndWindow(
(x:Int,y:Int)=>x+y,
Seconds(10),
Seconds(5)
)
// 8.降序处理后,取前3位
val sortResultRDD: DStream[(String, Int)] = resultDS.transform(rdd => {
// 降序处理
val sortRDD: RDD[(String, Int)] = rdd.sortBy(_._2, false)
// 取前3位
val top3RDD: Array[(String, Int)] = sortRDD.take(3)
println("--------------print top 3 begin--------------")
top3RDD.foreach(println)
println("--------------print top 3 end--------------")
sortRDD
})
// 打印排序后的数据
sortResultRDD.print()
// 9.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
}
7.spark Streaming整合flume实战
7.1.搭建flume环境
7.1.1.flume介绍
flume是Cloudera提供的一个高可用,高可靠,分布式的海量日志采集、聚合和传输的系统。flume支持在日志系统中定制各类数据发送方,用于收集数据 。同时,flume提供对数据进行简单处理,并写到各种数据接收方(可定制)的能力。
flume的三个组件:
Source:从数据发送器接收数据 ,并将接收的数据以flume的event格式传递给一个或者多个通道channel 。
Channel:channel是一种短暂的存储容器 ,它将从source处接收到的event格式的数据缓存起来 ,直到它们被sink消费。它在source和sink间起到桥梁的作用 。
Sink:sink将数据存储到集中存储器 。它从channal消费数据(events)并将其传递给目标地。 目标地可能是另一个sink,也可能HDFS或者HBase 等存储系统。
7.1.2.安装flume
7.1.2.1.下载flume
7.1.2.2.上传flume安装包到指定节点,并解压cd
7.1.2.3.修改配置文件
#node01
cd /export/servers/apache-flume-1.6.0-bin/conf
mv flume-env.sh.template flume-env.sh
vi flume-env.sh
#配置jdk环境变量
export JAVA_HOME=/export/servers/jdk1.8.0_141
7.1.2.4.编写agent配置文件
vi netcat-logger.conf
#定义agent 中各组件的名字
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# 描述和配置 source 组件:r1
a1.sources.r1.type = netcat
a1.sources.r1.bind = 192.168.53.100
a1.sources.r1.port = 44444
# 描述和配置 sink 组件:k1
a1.sinks.k1.type = logger
# 描述和配置 channel 组件,此处使用是内存缓存的方式
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# 描述和配置 source channel sink 之间的连接关系
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
7.1.2.5.初始体验
#启动agent(node01)
cd /export/servers/apache-flume-1.6.0-bin/bin
./flume-ng agent --conf conf --conf-file ../conf/netcat-logger.conf --name a1 -Dflume.root.logger=INFO,console
#node02
telnet 192.168.53.100 44444
7.1.3.flume整合spark Streaming
7.1.3.1.下载整合依赖包
7.1.3.2.将整合依赖包,上传到flume/lib目录
ll /export/servers/apache-flume-1.6.0-bin/lib
7.1.3.3.替换flume的scala依赖包
1.删除flume中原有的scala包
rm -rf scala-library-2.10.1.jar
2.从spark安装目录中拷贝scala安装包
cp /export/servers/spark-2.0.2-bin-hadoop2.7/jars/scala-library-2.11.8.jar /export/servers/apache-flume-1.6.0-bin/lib/
7.1.3.4.编写flume的agent配置文件
cd /export/servers/apache-flume-1.6.0-bin/conf
vi flume-poll-spark.conf
#定义agent中各组件的名字
a1.sources = r1
a1.sinks = k1
a1.channels = c1
#描述和配置source组件:r1
a1.sources.r1.channels = c1
a1.sources.r1.type = spooldir
a1.sources.r1.spoolDir =/export/servers/sparkflume
a1.sources.r1.fileHeader = true
#描述和配置channel组件,此处使用是内存缓存的方式
a1.channels.c1.type =memory
a1.channels.c1.capacity = 20000
a1.channels.c1.transactionCapacity=5000
#描述和配置sink组件:k1
a1.sinks.k1.channel = c1
a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
a1.sinks.k1.hostname=node01
a1.sinks.k1.port = 8888
a1.sinks.k1.batchSize= 2000
7.1.3.5准备数据文件
cd /export/servers/sparkflume
vi words.txt
hello me me
hello you
hello her
hello you you
7.1.3.6.启动flume的agent
#启动flume agent
cd /export/servers/apache-flume-1.6.0-bin
bin/flume-ng agent -n a1 -c conf/ -f conf/flume-poll-spark.conf -Dflume.root.logger=INFO,console
7.2.poll方式
7.2.1.导入依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-flume_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
7.2.2.编写案例代码
package cn.ly.flume
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
/**
* 学习spark Streaming从flume拉取数据,实现单词计数
*/
object WordCountByStreamingPollFlume {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf().setAppName("WordCountByStreamingPollFlume").setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
/**
* 细节:实现累加,需要设置checkpoint目录
*/
ssc.checkpoint("./flumeck")
// 4.通过FlumeUtils调用createPollingStream方法获取flume中的数据
/**
* 参数说明:
* 参数一:StreamingContext对象
* 参数二:flume的agent主机名称
* 参数三:flume的agent主机端口
*/
val flumeDS: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,"192.168.53.100",8888)
// 5.获取flume中event的body {"headers":xxxxxx,"body":xxxxx}
val dataLines: DStream[String] = flumeDS.map(x =>new String(x.event.getBody.array()))
// 6.切分每一行记录
val wordsDS: DStream[String] = dataLines.flatMap(_.split(" "))
// 7.每个单词计数为1
val wordAndOneDS: DStream[(String, Int)] = wordsDS.map((_,1))
// 8.实现所有批次结果数据累加
/**
* 实现方法:updateStateByKey
* 参数:myFunc
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.updateStateByKey(myFunc)
// 9.通过Output Operations操作打印数据
resultDS.print()
// 10.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
/**
* 定义实现批次结果数据累加函数
* 参数说明:
* currentValues:当前批次每个单词出现的所有1。比如:(spark,1)(spark,1)
* historyValues:之前所有批次中每个单词出现的总次数。比如:(spark,50)
*
* 返回值:
* 当前批次,与之前所有批次,每个单词出现的总次数(累加结果)
*/
def myFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int]={
// 将当前批次每个单词出现的所有1求和
val currentSum: Int = currentValues.sum
// 将当前批次单词求和结果,与之前批次总结果进行累加
val resultSum: Int = currentSum+historyValues.getOrElse(0)
Some(resultSum)
}
}
7.2.3.测试
图一:flume主机端
图二:spark Streaming程序端
7.2.4.关键步骤小结
1.导入依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-flume_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
2.通过FlumeUtils调用方法,拉取flume数据
// 4.通过FlumeUtils调用createPollingStream方法获取flume中的数据
/**
* 参数说明:
* 参数一:StreamingContext对象
* 参数二:flume的agent主机名称
* 参数三:flume的agent主机端口
*/
val flumeDS: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,"192.168.80.21",8888)
3.将拉取到的flume数据,获取body部分,并且转换成字符串内容
// 5.获取flume中event的body {"headers":xxxxxx,"body":xxxxx}
val dataLines: DStream[String] = flumeDS.map(x =>new String(x.event.getBody.array()))
7.3.push方式
7.3.1.编写flume的agent配置文件
cd /export/servers/apache-flume-1.6.0-bin/conf
vi flume-push-spark.conf
#定义agent中各组件的名字
a1.sources = r1
a1.sinks = k1
a1.channels = c1
#描述和配置source组件:r1
a1.sources.r1.channels = c1
a1.sources.r1.type = spooldir
a1.sources.r1.spoolDir =/export/servers/sparkflume
a1.sources.r1.fileHeader = true
#描述和配置channel组件,此处使用是内存缓存的方式
a1.channels.c1.type =memory
a1.channels.c1.capacity = 20000
a1.channels.c1.transactionCapacity=5000
#描述和配置sink组件:k1
a1.sinks.k1.channel = c1
a1.sinks.k1.type = avro
a1.sinks.k1.hostname=192.168.43.142
a1.sinks.k1.port = 9999
a1.sinks.k1.batchSize= 2000
**细节:**指定的是spark Streaming程序所在的主机Ip地址和端口(非虚拟机)
a1.sinks.k1.hostname=192.168.80.1
vi flume-push-spark.conf
7.3.2.编写案例代码
package cn.ly.flume
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
/**
* 学习spark Streaming从flume推送过来的数据,实现单词计数
*/
object WordCountByStreamingPushFlume {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf().setAppName("WordCountByStreamingPollFlume").setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
/**
* 细节:实现累加,需要设置checkpoint目录
*/
ssc.checkpoint("./flumeck")
// 4.通过FlumeUtils调用createStream方法获取flume中的数据
/**
* 参数说明:
* 参数一:StreamingContext对象
* 参数二:flume的agent主机名称
* 参数三:flume的agent主机端口
*/
val flumeDS: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"192.168.43.142",9999)
// 5.获取flume中event的body {"headers":xxxxxx,"body":xxxxx}
val dataLines: DStream[String] = flumeDS.map(x =>new String(x.event.getBody.array()))
// 6.切分每一行记录
val wordsDS: DStream[String] = dataLines.flatMap(_.split(" "))
// 7.每个单词计数为1
val wordAndOneDS: DStream[(String, Int)] = wordsDS.map((_,1))
// 8.实现所有批次结果数据累加
/**
* 实现方法:updateStateByKey
* 参数:myFunc
*/
val resultDS: DStream[(String, Int)] = wordAndOneDS.updateStateByKey(myFunc)
// 9.通过Output Operations操作打印数据
resultDS.print()
// 10.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
/**
* 定义实现批次结果数据累加函数
* 参数说明:
* currentValues:当前批次每个单词出现的所有1。比如:(spark,1)(spark,1)
* historyValues:之前所有批次中每个单词出现的总次数。比如:(spark,50)
*
* 返回值:
* 当前批次,与之前所有批次,每个单词出现的总次数(累加结果)
*/
def myFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int]={
// 将当前批次每个单词出现的所有1求和
val currentSum: Int = currentValues.sum
// 将当前批次单词求和结果,与之前批次总结果进行累加
val resultSum: Int = currentSum+historyValues.getOrElse(0)
Some(resultSum)
}
}
7.3.3.启动spark Streaming案例程序
7.3.4.启动flume的agent
cd /export/servers/apache-flume-1.6.0-bin
bin/flume-ng agent -n a1 -c conf/ -f conf/flume-push-spark.conf -Dflume.root.logger=INFO,console
7.3.5.关键步骤小结
1.通过FlumeUtils的createStream方法,接收flume推送的数据
// 4.通过FlumeUtils调用createStream方法获取flume中的数据
/**
* 参数说明:
* 参数一:StreamingContext对象
* 参数二:flume的agent主机名称
* 参数三:flume的agent主机端口
*/
val flumeDS: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"192.168.43.142",9999)
2.获取flume中event的body {“headers”:xxxxxx,“body”:xxxxx}
// 5.获取flume中event的body {"headers":xxxxxx,"body":xxxxx}
val dataLines: DStream[String] = flumeDS.map(x =>new String(x.event.getBody.array()))
3.优先启动spark Streaming应用
4.启动flume的agent
bin/flume-ng agent -n a1 -c conf/ -f conf/flume-push-spark.conf -Dflume.root.logger=INFO,console
5.在企业项目中,推荐使用从flume拉取数据的方式。推送数据可能会丢失数据
8.spark Streaming整合kafka实战
8.1.搭建kafka集群环境
8.1.1.kafka介绍
Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统 。
相关术语介绍:
术语 | 说明 |
---|---|
Broker | Kafka集群包含一个或多个服务器,服务器即broker |
Topic | 每条发布到Kafka集群的消息都有一个类别,类别即Topic |
Partition | Partition是物理上的概念,每个Topic包含一个或多个Partition |
Producer | 消息生产者,负责发布消息到Kafka broker |
Consumer | 消息消费者,向Kafka broker读取消息的客户端 |
Consumer Group | 每个Consumer属于一个特定的Consumer Group 。 如果没有指定group name,则属于默认的group |
kafka架构图:
8.1.2.集群环境搭建
8.1.2.1.下载kafka
官网地址:http://kafka.apache.org/downloads
8.1.2.2.上传到linux主机节点,并解压
#解压kafka
cd /export/servers
tar -zxvf kafka_2.11-0.10.2.1.tgz
#删除压缩包,并且重新命名kafka文件目录
rm -rf kafka_2.11-0.10.2.1.tgz
mv kafka_2.11-0.10.2.1/ kafka211010
图二:
8.1.2.3.配置kafka配置文件
#node01
cd /export/servers/kafka211010/config
vi server.properties
8.1.2.4.将配置好的kafka远程拷贝到其他节点
scp -r kafka211010/ root@node02:/export/servers/kafka211010
scp -r kafka211010/ root@node03:/export/servers/kafka211010
8.1.2.5.修改其他节点的server.properties配置
cd /export/servers/kafka211010/config
vim server.properties
#node02节点
broker.id=1
port=9092
host.name=hadoop02
#node03节点:
broker.id=2
port=9092
host.name=hadoop03
8.1.2.6.启动zookeeper集群
8.1.2.7.启动kafka服务器
集群节点:node01,node02,node03
#分别在三台节点执行
##启动kafka集群
/export/servers/kafka211010/bin/kafka-server-start.sh -daemon /export/servers/kafka211010/config/server.properties
## 停止kafka集群
/export/servers/kafka211010/bin/kafka-server-stop.sh
8.1.2.8.集群环境测试
-
查看topic list
#查看topic 列表 /export/servers/kafka211010/bin/kafka-topics.sh --list --zookeeper node01:2181,node02:2181,node03:2181 #查看具体某一个topic消息 /export/servers/kafka211010/bin/kafka-topics.sh --describe --zookeeper node01:2181,node02:2181,node03:2181 --topic topic1 #创建topic # --create:表示创建 # --zookeeper 后面的参数是zk的集群节点 # --replication-factor 3 :表示复本数 # --partitions 3:表示分区数 # --topic topic4:表示topic的主题名称 /export/servers/kafka211010/bin/kafka-topics.sh --create --zookeeper node01:2181,node02:2181,node03:2181 --replication-factor 3 --partitions 3 --topic topic4 #删除topic /export/servers/kafka211010/bin/kafka-topics.sh --delete --zookeeper node01:2181,node02:2181,node03:2181 --topic topic4
-
创建生产者
#创建生产者,生产消息 /export/servers/kafka211010/bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic topic5
-
创建消费者
#创建消费者,消费消息 /export/servers/kafka211010/bin/kafka-console-consumer.sh --zookeeper node01:2181,node02:2181,node03:2181 --topic topic5 --consumer-property group.id=my-consumer-g --delete-consumer-offsets --from-beginning 0
8.2.createDstream方式整合kafka
8.2.1.导入依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
8.2.2.启动zookeeper集群
8.2.3.启动kafka集群
/usr/local/develop/kafka211010/bin/kafka-server-start.sh -daemon /usr/local/develop/kafka211010/config/server.properties
8.2.4.创建topic
/export/servers/kafka211010/bin/kafka-topics.sh --create --zookeeper node01:2181 --replication-factor 1 --partitions 3 --topic kafka_spark
8.2.5.通过生产者,向topic发送消息
/export/servers/kafka211010/bin/kafka-console-producer.sh --broker-list node01:9092 --topic kafka_spark
8.2.6.编写spark Streaming案例程序
package cn.liny.kafka
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}
import scala.collection.immutable
/**
* 学习sparkStreaming对接kafka实现单词计数----采用receiver(高级API)
*/
object SparkStreamingKafka_Receiver {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf()
.setAppName("SparkStreamingKafka_Receiver")
.setMaster("local[4]")
.set("spark.streaming.receiver.writeAheadLog.enable","true")// 开启wal预写日志,保存数据源的可靠性
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
//设置checkpoint目录
ssc.checkpoint("./Kafka_Receiver")
// 4.通过KafkaUtils.createStream对接kafka
// 4.1.定义zk地址
val zkQuorum="node01:2181,node02:2181,node03:2181"
// 4.2.定义消费者组
val groupId="spark_receiver1"
// 4.3.定义topic相关信息 Map[String, Int]
val topics=Map("kafka_spark" -> 2)// 这里的value表示topic中每一个分区被N个线程消费
// 4.4.同时开启3个receiver接收数据
val receiverDstream: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
stream
}
)
// 4.5.使用ssc.union方法合并所有的receiver中的数据
val unionDStream: DStream[(String, String)] = ssc.union(receiverDstream)
// 5.获取topic中的数据
val topicData: DStream[String] = unionDStream.map(_._2)
// 6.切分每一行,每个单词计为1
val wordAndOne: DStream[(String, Int)] = topicData.flatMap(_.split(" ")).map((_,1))
// 7.相同单词出现的次数累加
val resultDS: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
// 8.通过Output Operations操作打印数据
resultDS.print()
// 9.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
}
8.2.7.关键步骤小结
1.创建SparkConf对象,设置的线程数要大于receiver个数。同时要开启wal预写日志,保证数据的不丢失。
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf()
.setAppName("SparkStreamingKafka_Receiver")
.setMaster("local[4]")
.set("spark.streaming.receiver.writeAheadLog.enable","true")// 开启wal预写日志,保存数据源的可靠性
2.通过KafkaUtils.createStream对接kafka
// 4.通过KafkaUtils.createStream对接kafka
// 4.1.定义zk地址
val zkQuorum="hadoop01:2181,hadoop02:2181,hadoop03:2181"
// 4.2.定义消费者组
val groupId="spark_receiver1"
// 4.3.定义topic相关信息 Map[String, Int]
val topics=Map("kafka_spark" -> 2)// 这里的value表示topic中每一个分区被N个线程消费
// 4.4.同时开启3个receiver接收数据
val receiverDstream: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
stream
}
)
// 4.5.使用ssc.union方法合并所有的receiver中的数据
val unionDStream: DStream[(String, String)] = ssc.union(receiverDstream)
3.获取topic中的数据
// 5.获取topic中的数据
val topicData: DStream[String] = unionDStream.map(_._2)
4.基于receiver的方式:是使用Kafka的高级API,topic的offset偏移量在ZooKeeper中。这种方式配合WAL预写机制,可以保证数据零丢失,但是可能会出现相同的数据,被处理两次。
8.3.createDirectStream方式整合kafka
8.3.1.编写案例代码
package cn.liny.kafka
import kafka.serializer.StringDecoder
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
/**
* 学习sparkStreaming对接kafka实现单词计数----采用Direct(低级API)
*/
object SparkStreamingKafka_Direct {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf对象
val conf: SparkConf = new SparkConf()
.setAppName("SparkStreamingKafka_Direct")
.setMaster("local[2]")
// 2.创建SparkContext对象
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
// 3.创建StreamingContext对象
/**
* 参数说明:
* 参数一:SparkContext对象
* 参数二:每个批次的间隔时间
*/
val ssc: StreamingContext = new StreamingContext(sc,Seconds(5))
//设置checkpoint目录
ssc.checkpoint("./Kafka_Direct")
// 4.通过KafkaUtils.createDirectStream对接kafka(采用是kafka低级api偏移量不受zk管理)
// 4.1.配置kafka相关参数
val kafkaParams=Map("metadata.broker.list"->"192.168.53.100:9092,192.168.53.110:9092,192.168.53.120:9092","group.id"->"kafka_Direct")
// 4.2.定义topic
val topics=Set("kafka_spark")
val dstream: InputDStream[(String, String)] = KafkaUtils
.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics)
// 5.获取topic中的数据
val topicData: DStream[String] = dstream.map(_._2)
// 6.切分每一行,每个单词计为1
val wordAndOne: DStream[(String, Int)] = topicData.flatMap(_.split(" ")).map((_,1))
// 7.相同单词出现的次数累加
val resultDS: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
// 8.通过Output Operations操作打印数据
resultDS.print()
// 9.开启流式计算
ssc.start()
// 阻塞一直运行
ssc.awaitTermination()
}
}
8.3.2.关键步骤小结
1.通过KafkaUtils .createDirectStream对接kafka
// 4.通过KafkaUtils.createDirectStream对接kafka(采用是kafka低级api偏移量不受zk管理)
// 4.1.配置kafka相关参数
val kafkaParams=Map("metadata.broker.list"->"192.168.80.21:9092,192.168.80.22:9092,192.168.80.23:9092","group.id"->"kafka_Direct")
// 4.2.定义topic
val topics=Set("kafka_spark")
val dstream: InputDStream[(String, String)] = KafkaUtils
.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics)
2.Direct方式的优点:
-
简化并行
不需要创建多个kafka输入流,然后union它们。sparkStreaming将会创建和kafka分区数相同的rdd的分区数,并行读取数据。
-
高效
receiver实现方式,数据的零丢失是将数据预先保存在WAL中,会导致数据要拷贝两次,效率低下。Direct方式不需要拷贝两次数据,直接获取kafka的topic数据即可处理。
-
恰好一次语义(Exactly-once-semantics)
Receiver读取kafka数据是通过kafka高层次api,把偏移量写入zookeeper集群中。可能会因为sparkStreaming和zookeeper中保存的偏移量不一致而导致数据被消费了多次。
Exactly-once-semantics通过实现kafka低层次api,偏移量仅仅被ssc保存在checkpoint目录中,消除了zk和ssc偏移量不一致的问题。