文章目录
知识储备
1、数据处理的方式:
- 流式数据处理(Streaming)
- 批量数据处理(batch)
2、数据处理延迟的长短
- 实时数据处理:毫秒级别
- 离线数据处理:小时 or 天级别
3、Spark streaming 是准实时(秒、分钟)、微批次(时间)的数据处理框架
1 基本概念
1、Spark streaming用于流式数据的处理。
- 支持的数据输入源很多,例如:kafka、Flume、Twitter、ZeroMQ和简单的TCP套接字等
- 数据输入后可以使用Spark的高度抽象原语如:map、reduce、join、window等进行运算
- 结果可以保存在很多地方,例如HDFS、数据库等
2、Spark Streaming使用离散化流作为抽象表示,叫做DStream,是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为RDD存在,而DStream是由这些RDD组成的序列。
3、Spark Streaming程序基本步骤
- 通过创建输入DStream来定义输入源。
- 通过对DStream应用转换操作和输出操作来定义流计算。
- 使用streamingContext.start()来开始接收数据和处理流程。
- 通过streamingContext.awaitTermination()方法来等待处理结束(手动结束或因为错误而结束)。
- 可以通过streamingContext.stop()来手动结束流计算进程。
2 WordCount入门
object word {
def main(args: Array[String]): Unit = {
// 创建环境对象,传递两个参数,第一个参数表示环境配置,第二个参数表示批量处理的周期(采集周期)
val conf = new SparkConf().setMaster("local").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
// 逻辑处理
// 获取端口数据
val lines = ssc.socketTextStream("brace", 9999)
val words = lines.flatMap(_.split(" "))
val wordToOne = words.map((_,1))
val WordToCount = wordToOne.reduceByKey(_+_)
WordToCount.print()
// 关闭环境,由于SparkStreaming采集器是长期执行的任务,不能直接关闭
// 如果main方法执行完毕,应用程序也会自动结束,所以不能让main方法执行完毕
// ssc.stop()
// 1、启动采集器
ssc.start()
// 2、等待采集器的关闭
ssc.awaitTermination()
}
}
在虚拟机输入:nc -lp 9999
,传入数据
3 DStream的创建
3.1 文件数据源
3.1.1 用法和说明
文件数据流:能够读取所有HDFS API兼容的文件系统文件,通过fileStream方法进行读取,SparkStreaming会监控dataDirectory目录并不断处理移动进来的文件。
streamingContext.textFileStream(dataDirectory)
注意:
- 文件需要有相同的数据格式
- 文件进入目录的方式需要通过移动或重命名来实现
- 一旦文件移动进目录,则不能修改
3.2 RDD队列
3.2.1 用法
测试过程中,通过ssc.queueStream(queueOfRDDs)
创建DStream,每一个推送到这个队列的RDD都会作为一个DStream处理
3.2.2 案例
object word2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
// 创建RDD队列
val rddQueue = new mutable.Queue[RDD[Int]]()
val inputStream = ssc.queueStream(rddQueue,false)
val mappedStream = inputStream.map((_,1))
val reducedStream = mappedStream.reduceByKey(_+_)
reducedStream.print()
// 1、启动采集器
ssc.start()
for(i<- 1 to 5){
rddQueue += ssc.sparkContext.makeRDD(1 to 300,10)
Thread.sleep(2000)
}
// 2、等待采集器的关闭
ssc.awaitTermination()
}
}
3.3 自定义数据源
继承Receiver
,实现onStart
、onStop
方法来自定义数据采集
object word3 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
val messageDS = ssc.receiverStream(new MyReceiver())
messageDS.print()
// 1、启动采集器
ssc.start()
// 2、等待采集器的关闭
ssc.awaitTermination()
}
/**
* 自定义数据采集器
*/
class MyReceiver extends Receiver[String](StorageLevel.MEMORY_ONLY){
private var flag = true
override def onStart()={
new Thread(new Runnable {
override def run(): Unit = {
while(flag){
val msg = "采集的数据为:"+new Random().nextInt(10).toString
store(msg)
Thread.sleep(500)
}
}
}).start()
}
override def onStop()={
flag=false
}
}
}
3.4 Kafka数据源
3.4.1 版本
DirectAPI:由计算的Executor来主动消费Kafka的数据
3.4.2 Kafka 0-10 Direct
1、添加依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.1</version>
</dependency>
2、代码
object word4 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
// 配置
val kafkaParams = Map[String, Object](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "brace:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "aaa",
"key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
"value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
)
// 工具类
val kafkaDtatDS = KafkaUtils.createDirectStream[String,String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String,String](Set("aaa"),kafkaParams)
)
kafkaDtatDS.map(_.value()).print()
ssc.start()
ssc.awaitTermination()
}
}
3、创建topic
- 查询
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server brace:9092 --list
or
/usr/local/kafka/bin/kafka-topics.sh --list --zookeeper brace:2181
- 创建
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server brace:9092 --create --topic aaa --partitions 3 --replication-factor 1
or
/usr/local/kafka/bin/kafka-topics.sh --create --zookeeper brace:2181 --replication-factor 1 --partitions 3 --topic aaa
- 使用
/usr/local/kafka/bin/kafka-console-producer.sh --broker-list brace:9092 --topic aaa
- 消费
/usr/local/kafka/bin/kafka-console-consumer.sh --bootstrap-server brace:9092 --topic aaa --from-beginning
3.5 Flume
3.5.1
4 DStream转换
DStream上的操作与RDD相似,分为Transformations(转换)和Output Operations(输出)两种,此外转换操作中还有一些比较特殊的原语,如:updateStateByKey()、transform()
- 无状态转换:每个批次的处理不依赖于之前批次的数据。
- 有状态转换:当前批次的处理需要使用之前批次的数据或者中间结果。有状态转换包括基于滑动窗口的转换和追踪状态变化的转换(updateStateByKey)。
4.1 无状态转化操作
无状态操作就是把简单的RDD转化操作应用的每个批次上,也就是转化DStream中的每一个RDD。
Transform
通过对源DStream的每个RDD应用RDD-to-RDD函数,创建一个新的DStream。支持在新的DStream中做任何RDD操作。
join
两个流之间的join将相同key的数据连接到一起
4.2 有状态转化操作
4.2.1 UpdateStateByKey
用于记录历史记录,有时候我们需要在DStream中跨批次维护状态(例如wordcount),针对这种情况该方法为我们提供了一个状态变量的访问,用于键值对形式的Dstream。
UpdateStateByKey的结果是一个新的DStream,其内部的RDD序列是由每个时间区间对应的(键,状态)对组成的
两步:
- 定义状态,状态可以是任意的数据类型
- 定义状态更新函数
例子
object word5 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
ssc.checkpoint("cp")
val datas = ssc.socketTextStream("brace",9999)
val wordToOne = datas.map((_,1))
// updateStateByKey根据key对数据的状态进行更新
// 传递的参数中含有两个值
// 第一个值表示相同key的value数据
// 第二个值表示缓存区相同key的value数据
val state = wordToOne.updateStateByKey (
(seq:Seq[Int], buff:Option[Int])=>{
val newCount = buff.getOrElse(0)+seq.sum
Option(newCount)
}
)
state.print()
ssc.start()
ssc.awaitTermination()
}
}
4.2.2 Window Operations
可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming的允许状态。基于窗口的操作会在一个比streamingcontext的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。
应用场景
变化(交通流量等)
object word6 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
val kafkaParams = Map[String, Object](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "brace:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "aaa",
"key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
"value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
)
val kafkaDataDS = KafkaUtils.createDirectStream[String,String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String,String](Set("aaa"),kafkaParams)
)
val value = kafkaDataDS.map(_.value())
// 参数:间隔(大小:采集周期的整数倍)、滑动间隔(步长:采集周期的整数倍)
value.window(Seconds(9),Seconds(3))
value.print()
ssc.start()
ssc.awaitTermination()
}
}
5 DStream输出
输出操作如下:
print()
saveAsTextFile(prefix,[suffix])
:以txt文件形式存储DStream的内容saveAsObjectFiles(prefix,[suffix])
:以java对象序列化的方式将数据保存为SequenceFilessaveAsHadoopFiles
foreachRDD()
import java.sql.{PreparedStatement, Connection, DriverManager}
object word8 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
val ssc = new StreamingContext(conf,Seconds(3))
val lines = ssc.socketTextStream("brace", 9998)
val words = lines.flatMap(_.split(" "))
val wordToOne = words.map((_,1))
val WordToCount = wordToOne.reduceByKey(_+_)
WordToCount.print()
WordToCount.foreachRDD(rdd => {
//内部函数
def func(records: Iterator[(String,Int)]) {
var conn: Connection = null
var stmt: PreparedStatement = null
try {
val url = "jdbc:mysql://localhost:3306/spark"
val user = "root"
val password = "Hive@2020"
conn = DriverManager.getConnection(url, user, password)
records.foreach(p => {
val sql = "insert into wordcount(word,count) values (?,?)"
stmt = conn.prepareStatement(sql);
stmt.setString(1, p._1.trim)
stmt.setInt(2,p._2.toInt)
stmt.executeUpdate()
})
} catch {
case e: Exception => e.printStackTrace()
} finally {
if (stmt != null) {
stmt.close()
}
if (conn != null) {
conn.close()
}
}
}
val repartitionedRDD = rdd.repartition(3)
repartitionedRDD.foreachPartition(func)
})
ssc.start()
ssc.awaitTermination()
}
}