1、Spark Streaming的环境搭建:
1、在原先的基础上添加依赖:
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.12</artifactId>
<version>3.1.3</version>
</dependency>
2、构建环境:
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Durations, StreamingContext}
object Demo01Streaming {
def main(args: Array[String]): Unit = {
/**
* 因为Spark Streaming是微批处理的,所以在创建是可以指定批次的大小
*/
val spark: SparkSession = SparkSession
.builder()
.appName(this.getClass.getSimpleName.replace("$", ""))
//因为流处理需要一直接收数据,所以会一直占用一个线程,所以至少需要两个以上的线程才能保证任务的正常执行
.master("local[2]")
.getOrCreate()
//1、使用旧的版本创建SparkStreaming环境,取得名称一般行业规定的使用ssc
val ssc: StreamingContext = new StreamingContext(spark.sparkContext, Durations.seconds(5)) //5表示的是SparkStreaming会五秒钟就会自动的触发一次任务
/**
* 在需要监听的节点上启动socket服务,这里是在master上启动的socket服务
* 在master启动socket服务的命令:
* nc -lk master 8888
* 如果nc没有需要下载:
* yum install nc
*
*
* 时间对齐策略:只有在第一次提交任务的时候是需要进行时间对齐的,一天时间会按照批次的大小进行均匀的划分,无论何时提交的任务,只要到达划分好的时间就会自动的触发执行任务。
*/
val textDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
//每个五秒钟进行输出一次
textDS.print()
//Streaming环境构建好后,需要启动streaming服务
ssc.start()
//等待任务结束
ssc.awaitTermination()
//停止任务
ssc.stop()
}
}
2、有状态算子:UpdataStateByKey
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Durations, StreamingContext}
object Demo03UpdateStateByKey {
def main(args: Array[String]): Unit = {
/**
* 需要接受两个参数的算子有:sortWith、reduce、mapPartitionWithIndex、reduceByKey
*
* 有状态算子:UpdateStateByKey
* 作用:就是可已经当前批次和上一个批次相同key对应的value进行整合
*/
val spark: SparkSession = SparkSession
.builder()
.appName(this.getClass.getSimpleName.replace("$", ""))
.master("local[2]")
.getOrCreate()
//搭建SparkStreaming的环境
val ssc: StreamingContext = new StreamingContext(spark.sparkContext, Durations.seconds(5))
//如果使用了有状态算子,就需要指定CheckPoint的目录,类似于虚拟机中的快照的功能,当程序出现问题的时候可以回溯到指定的位置
ssc.checkpoint("spark/data/ck")
val textDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
textDS
.flatMap(k=>{k.split(",")})
.map(word=>(word,1))
/**
* 1、需要传入两个参数
* 2、每个参数的含义:
* sep:Seq[Int]:表示的是在分组结束之后,会将相同的key当前批次中的所有的value构成seq
* opt:[Int]:表示的是上一个批次中key对应value的计算结果,如果是第一个批次,则没有上一个批次,所以类型为Option
* 就是将上一个批次中的value的个数和当前批次中的统计的结果之和。当下次计算时,会将上一个批次的计算结果存放在opt中
*/
.updateStateByKey((seq:Seq[Int],opt:Option[Int])=>{
//统计当前批次中的value的数量
val sumSeq: Int = seq.sum
opt match {
case Some(v)=>
//有上一次就将上一次的和这一次的结果做累加
Some(v+sumSeq)
case None =>
//如果没有上一次,就返回当前批次的结果
Some(sumSeq)
}
})
.print()
//启动Streaming服务
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
3、滑动窗口、滚动窗口
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Durations, StreamingContext}
object Demo04ReduceByKeyANDWindow {
def main(args: Array[String]): Unit = {
/**
*需求:每隔5秒,统计一下最近15秒钟内的单词的数量
* 该需求中总共有两种时间因素分别是:
* 批次的大小(又称为窗口的大小)
* 计算任务的时间间隔
* 时间窗口主要分成两种:滚动窗口和滑动窗口
* 滚动窗口:窗口的大小是等于计算任务的时间间隔,并且每一个窗口之间都是独立的
* 滑动窗口:窗口的大小小于计算任务的时间间隔,并且窗口之间是有数据重叠的
*
*/
val spark: SparkSession = SparkSession
.builder()
.appName(this.getClass.getSimpleName.replace("$", ""))
//一定需要指定两个并行度,一个需要一直接收数据
.master("local[2]")
.getOrCreate()
//构建SparkString环境
val ssc: StreamingContext = new StreamingContext(spark.sparkContext, Durations.seconds(5))
val textDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
//需求:每隔5秒,统计一下最近15秒钟内的单词的数量
textDS
//将接受的数据进行展平是哦与纳贡逗号进行切分
.flatMap(kv=>kv.split(","))
.map(kv=>{(kv,1)})
//指定窗口的的大小
.reduceByKeyAndWindow((i1,i2)=>{
i1+i2
},Durations.seconds(15))
.print()
//启动streaming的服务
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
4、Transform:
作用和foreachRDD的作用基本一致,都是将每一个批次转换成RDD来处理,但是二者的区别就在于Transform是有返回值的,所以可以理解为Transform是一个转换算子,然而froeachRDD是没有返回值的
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.streaming.dstream.ReceiverInputDStream
object Demo05Transform {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.appName(this.getClass.getSimpleName.replace("$", ""))
//一定需要指定两个并行度,一个需要一直接收数据
.master("local[2]")
.getOrCreate()
/**
* Transform:将每一个批次转换成RDD,作用和foreachRDD的作用基本一致,区别就是foreachRDD是没有返回值,
* 然而Transform是有返回值的,所以Transform是一个转换算子,后面还可以借其他的操作
*/
//构建SparkString环境
val ssc: StreamingContext = new StreamingContext(spark.sparkContext, Durations.seconds(5))
val textDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
//需求使用transform来统计单词的个数
textDS
.transform(
kv=>{
kv
.flatMap(kv=>kv.split(","))
.map(word=>(word,1))
.reduceByKey(_+_)
}
)
.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
5、缉查布控:
主要注意的就是数据库的连接的时刻:
1、如果在算子外部建立连接,当数据库中的数据发生变化,对于查询数据,丢失的数据依旧会被查询到,因为在算子外部,只会建立一次连接
2、如果建立在算子内部,就会导致查询一次就会建立一次连接,会导致效率较低
3、所以综上所述选择每一个批次时建立一次连接。
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Durations, StreamingContext}
import java.sql.{Connection, DriverManager, ResultSet, Statement}
import scala.collection.mutable
object Demo06JhaBuKong {
def main(args: Array[String]): Unit = {
/**
* 缉查布控:是一种实时的处理,实时的更新信息,
* 需求:查询黑名单时候,黑名单的信息是存储在MySql中,外界在使用时候只需要输出该用户
* 的手机号码,就可以了解该用户的是否是在黑名单中,并且获取该用户的所有信息
*/
//首先构建SparkSession
val spark: SparkSession = SparkSession
.builder()
.appName(this.getClass.getSimpleName.replace("$", ""))
.master("local[2]")
.getOrCreate()
//搭建SparkStreaming的环境
val ssc: StreamingContext = new StreamingContext(spark.sparkContext, Durations.seconds(5))
val textDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)
//与MySQL建立连接
/**
* 需要考虑的问题:
* 1、什么时候建立连接,在Driver中建立连接会导致当数据库中的数据删除的时候该数据依旧可以查询出来,没有实时更新,
* 如果在在算子内部构建,就会导致查询一条数据就需要建立一次链接,会导致效率低,还有可能会使数据库拒绝连接
*
*
* 所以综上所述:在每一个批次的时候建立MySql的连接
*/
textDS
.transform(rdd=>{
//此时Transform会将每一个批次转化成RDD
//此时建立连接
val conn: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")
//建立连接
val st: Statement = conn.createStatement()
val rs: ResultSet = st.executeQuery("select mdn from mdn_block")
//创建一个set集合
val mdnBlacklist: mutable.Set[String] = mutable.Set[String]()
while(rs.next){
//将查询的值添加到该set集合中
mdnBlacklist.add(rs.getString("mdn"))
}
//黑名单已经出来根据已有的和名单的信息范该黑名的用户的信息全部提取出来
rdd
.filter(kv=>{
val phone: String = kv.split(",")(0)
mdnBlacklist.contains(phone)
})
.map(kv1=>{
val line: Array[String] = kv1.split(",")
(line(0),line(2),line(3),line(5),line(6))
})
}).print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
6、StructuredStreaming
import org.apache.spark.sql.streaming.OutputMode
import org.apache.spark.sql.{DataFrame, Dataset, Row, SaveMode, SparkSession}
object Demo07StructuredStreaming {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession
.builder()
.appName(this.getClass.getSimpleName.replace("$", ""))
.master("local[2]")
.config("spark.sql.shuffle.partitions", "2")
.getOrCreate()
// 通过SparkSession readStream加载流式数据
val textDS: DataFrame = spark
.readStream
.format("socket")
.option("post", 8888)
.option("host", "master")
.load
//使用sql或者是DSL的方式统计单词的个数
// 通过SQL或者是DSL完成实时数据处理
// 状态会自动进行维护
// 看似类似于实时,但是延时是看起来比较低,并不是实时,实际上还是微批处理,只不过批次非常小是ms级别
textDS.createOrReplaceTempView("textTB")
spark.sql(
"""
|
|select
| t1.word
| count(word) as word_count
|from
|(
|select
|explode(split(value,",")) as word
|from
|textTB
|) t1
|""".stripMargin)
.writeStream
.outputMode("complete")
.format("console") //数据以流的形式写出去
// .start()//启动任务
// .awaitTermination()//等待停止
import spark.implicits._
import org.apache.spark.sql.functions._
textDS
.select(explode(split($"value",",")) as "word")
.groupBy($"word")
.agg(count($"word"))
.writeStream
/**
* outputMode有三种方式:
* 1、Complete:每次都将所有结果进行输出,只能用于包含了聚合操作的流处理
* 2、Append:每次将新增的rows进行输出,只能用于不包含聚合操作的流处理
* 3、Update:所有场景都适用,每次只会将新增或者有变化的结果进行输出
*/
.outputMode(OutputMode.Update())
// .start()
// .awaitTermination()
// 将wordCnt的结果写入MySQL
textDS
.select(explode(split($"value", ",")) as "word")
.groupBy($"word")
.agg(count("*") as "cnt")
.writeStream
.outputMode(OutputMode.Complete())
// 将每个批次转换成DataFrame进行处理
.foreachBatch((df: Dataset[Row], l: Long) => {
println(l) // 批次的编号
df.write
.format("jdbc")
.option("url", "jdbc:mysql://master:3306/student")
.option("dbtable", "word_cnt")
.option("user", "root")
.option("password", "123456")
.option("truncate", "true") // 以truncate的方式实现Overwrite,避免表结构发生变化
.mode(SaveMode.Overwrite)
.save()
})
.start()
.awaitTermination()
}
}