Spark Streaming
文章目录
第1节 Spark Streaming概述
第2节 DStream基础数据源
基础数据源包括:文件数据流、socket数据流、RDD队列流;这些数据源主要用于测试。
引入依赖:
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
2.1 文件数据流
文件数据流:通过 textFileStream(directory) 方法进行读取 HDFS 兼容的文件系统文件
Spark Streaming 将会监控 directory 目录,并不断处理移动进来的文件
- 不支持嵌套目录
- 文件需要有相同的数据格式
- 文件进入 directory 的方式需要通过移动或者重命名来实现
- 一旦文件移动进目录,则不能再修改,即便修改了也不会读取新数据
- 文件流不需要接收器(receiver),不需要单独分配CPU核
package cn.lagou.Streaming
import org.apache.log4j.{
Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{
Seconds, StreamingContext}
object FileDSteam {
def main(args: Array[String]): Unit = {
//初始化
Logger.getLogger("org").setLevel(Level.WARN) //屏蔽日志
val conf = new SparkConf().setAppName(this.getClass.getCanonicalName)
.setMaster("local[*]")
// 创建StreamingContext
// StreamingContext是所有流功能函数的主要访问点,这里使用多个执行线程和 10秒的批次间隔来创建本地的StreamingContext
val ssc = new StreamingContext(conf, Seconds(10)) //设置10s启动一次
//创建Dstream
// 这里采用本地文件,也可以采用HDFS文件
val lines: DStream[String] = ssc.textFileStream("data/log")
//Dstream转换
val words: DStream[String] = lines.flatMap(_.split("\\s+"))
val result: DStream[(String, Int)] = words.map((_, 1)).reduceByKey(_ + _)
//Dstream输出
result.print(20)
//启动作业
ssc.start()
ssc.awaitTermination()
}
}
注:文件夹下只有新文件才会读取
2.2 Socket数据流
Spark Streaming可以通过Socket端口监听并接收数据,然后进行相应处理;
新开一个命令窗口,启动 nc 程序:
nc -lk 9999
# yum install nc
随后可以在nc窗口中随意输入一些单词,监听窗口会自动获得单词数据流信息,在监听窗口每隔x秒就会打印出词频统计信息,可以在屏幕上出现结果。
备注:使用local[*],可能存在问题。
如果给虚拟机配置的cpu数为1,使用local[*]
也只会启动一个线程,该线程用于receiver task,此时没有资源处理接收达到的数据。
【现象:程序正常执行,不会打印时间戳,屏幕上也不会有其他有效信息】
注意:DStream的 StorageLevel 是 MEMORY_AND_DISK_SER_2;
package cn.lagou.Streaming
import org.apache.log4j.{
Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{
Seconds, StreamingContext}
object SocketDStream{
def main(args: Array[String]): Unit = {
//初始化
Logger.getLogger("org").setLevel(Level.WARN) //屏蔽日志
val conf = new SparkConf().setAppName(this.getClass.getCanonicalName)
.setMaster("local[*]")
// 创建StreamingContext
// StreamingContext是所有流功能函数的主要访问点,这里使用多个执行线程和 10秒的批次间隔来创建本地的StreamingContext
val ssc = new StreamingContext(conf, Seconds(10)) //设置10s启动一次
//创建Dstream
// 这里采用本地文件,也可以采用HDFS文件
//val lines: DStream[String] = ssc.textFileStream("data/log")
val lines = ssc.socketTextStream("linux122", 9999)
//Dstream转换
val words: DStream[String] = lines.flatMap(_.split("\\s+"))
val result: DStream[(String, Int)] = words.map((_, 1)).reduceByKey(_ + _)
//Dstream输出
result.print(20)
//启动作业
ssc.start()
ssc.awaitTermination()
}
}
SocketServer程序(单线程),监听本机指定端口,与socket连接后可发送信息:
package cn.lagou.Streaming
import java.io.PrintWriter
import java.net.{
ServerSocket, Socket}
import scala.util.Random
object SocketLikeNC {
def main(args: Array[String]): Unit = {
val words: Array[String] = "Hello World Hello Hadoop Hellospark kafka hive zookeeper hbase flume sqoop".split(" \\ s + ")
val n: Int = words.length
val port: Int = 9999
val random: Random = scala.util.Random
val server = new ServerSocket(port)
val socket: Socket = server.accept()
println("成功连接到本地主机:" + socket.getInetAddress)
while (true) {
val out = new PrintWriter(socket.getOutputStream)
out.println(words(random.nextInt(n)) + " " +
words(random.nextInt(n)))
out.flush()
Thread.sleep(100)
}
}
}
将之前SocketDStream里面,
val lines = ssc.socketTextStream(“linux122”, 9999)
改为本都服务
val lines = ssc.socketTextStream(“localhost”, 9999)
先启动SocketLikeNC,再启动SocketDStream
SocketServer程序(多线程)
package cn.lagou.Streaming
import java.net.ServerSocket
object SocketServer {
def main(args: Array[String]): Unit = {
val server = new ServerSocket(9999)
println(s"Socket Server 已启动: ${server.getInetAddress}:${server.getLocalPort}")
while (true) {
val socket = server.accept()
println("成功连接到本地主机:" + socket.getInetAddress)
new ServerThread(socket).start()
}
}
}
package cn.lagou.Streaming
import java.io.DataOutputStream
import java.net.Socket
class ServerThread(sock: Socket) extends Thread {
val words = "hello world hello spark hello word hello java hello hadoop hello kafka"
.split("\\s+")
val length = words.length
override def run(): Unit = {
val out = new DataOutputStream(sock.getOutputStream)
val random = scala.util.Random
while (true) {
val (wordx, wordy) = (words(random.nextInt(length)), words(random.nextInt(length)))
out.writeUTF(s"$wordx $wordy")
Thread.sleep(100)
}
}
}
2.3 RDD队列流
调试Spark Streaming应用程序的时候,可使用streamingContext.queueStream(queueOfRDD) 创建基于RDD队列的DStream;
备注:
- oneAtATime:缺省为true,一次处理一个RDD;设为false,一次处理全部RDD
- RDD队列流可以使用local[1]
- 涉及到同时出队和入队操作,所以要做同步
例:每秒创建一个RDD(RDD存放1-100的整数),Streaming每隔1秒就对数据进行处理,计算RDD中数据除10取余的个数。
package cn.lagou.Streaming
import org.apache.log4j.{
Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.{
Seconds, StreamingContext}
import scala.collection.mutable
object RDDQueueDStream {
def main(args: Array[String]): Unit = {
Logger.getLogger("org").setLevel(Level.WARN)
val sparkConf = new
SparkConf().setAppName(this.getClass.getCanonicalName).setMaster(
"local[2]")
// 每隔1秒对数据进行处理
val ssc = new StreamingContext(sparkConf, Seconds(1))
//创建DStream
val rddQueue = new mutable.Queue[RDD[Int]]()
val queueStream = ssc.queueStream(rddQueue) //从队列中拿到DS
val mappedStream = queueStream.map(r => (r % 10, 1)) //DS中每个元素除10取余,做成key\value对
val reducedStream = mappedStream.reduceByKey(_ + _)
reducedStream.print()
ssc.start()
// 每秒产生一个RDD,将RDD放置在队列中
for (i <- 1 to 5){
rddQueue.synchronized {
val range = (1 to 100).map(_*i)
rddQueue += ssc.sparkContext.makeRDD(range, 2) //2 代表两个分区
}
Thread.sleep(2000)
}
ssc.stop()
}
}
第3节 DStream转换操作
DStream上的操作与RDD的类似,分为 Transformations(转换)和 Output Operations(输出)两种,此外转换操作中还有一些比较特殊的方法,如:
updateStateByKey、transform 以及各种 Window 相关的操作。
备注:
- 在DStream与RDD上的转换操作非常类似(无状态的操作)
- DStream有自己特殊的操作(窗口操作、追踪状态变化操作)
- 在DStream上的转换操作比RDD上的转换操作少
DStream 的转化操作可以分为 无状态(stateless) 和 有状态(stateful) 两种:
- 无状态转化操作。每个批次的处理不依赖于之前批次的数据。常见的 RDD 转化操作,例如 map、filter、reduceByKey 等
- 有状态转化操作。需要使用之前批次的数据 或者是 中间结果来计算当前批次的数据。有状态转化操作包括:基于滑动窗口的转化操作 或 追踪状态变化的转化操作
3.1 无状态转换
无状态转化操作就是把简单的 RDD 转化操作应用到每个批次上,也就是转化DStream 中的每一个 RDD。
常见的无状态转换包括:map、flatMap、filter、repartition、reduceByKey、groupByKey;直接作用在DStream上
重要的转换操作:transform。通过对源DStream的每个RDD应用RDD-to-RDD函数,创建一个新的DStream。支持在新的DStream中做任何RDD操作。
这是一个功能强大的函数,它可以允许开发者直接操作其内部的RDD。也就是说开发者,可以提供任意一个RDD到RDD的函数,这个函数在数据流每个批次中都被调用,生成一个新的流。
示例:黑名单过滤
假设:arr1为黑名单数据(自定义),true表示数据生效,需要被过滤掉;false表示数据未生效
val arr1 = Array((“spark”, true), (“scala”, false))
假设:流式数据格式为"time word",需要根据黑名单中的数据对流式数据执行过滤操作。如"2 spark"要被过滤掉
1 hadoop
2 spark
3 scala
4 java
5 hive
结果:“2 spark” 被过滤
方法一:使用外连接
package cn.lagou.Streaming.basic
import org.apache.log4j.{
Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.