I know, i know
地球另一端有你陪我
一、Spark - streaming
微批处理,一定时间内将该段时间产生的数据进行批处理,是一种近似的实时处理
1、WordCount
package streaming
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object Demo1WordCount {
def main(args: Array[String]): Unit = {
/**
* 创建spark 环境
* local 模式需要至少两个线程
* 后台会需要一个线程接收socket
* 另一个处理信息
*
*/
val conf: SparkConf = new SparkConf()
.setAppName("Demo1WordCount")
.setMaster("local[2]")
val sc: SparkContext
= new SparkContext(conf)
/**
* 创建spark streaming环境
* 指定每隔多久计算一次
*/
val ssc: StreamingContext
= new StreamingContext(sc, Durations.seconds(5))
/**
* 读取数据
* yum install nc
* 读取socket 数据
* nc -lk 8888
* 用于测试
*/
val linesDS: ReceiverInputDStream[String]
= ssc.socketTextStream("master", 8888)
/**
* 处理数据
*/
linesDS
.flatMap(_.split(","))
.map((_, 1))
.reduceByKey(_ + _)
//DS 中使用 print 打印
.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
2、UpdateStateByKey
带状态算子(名字品味真的很差)
总之就是可以保留之前批次留下来的数据
package streaming
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object Demo2UpdateStateByKey {
def main(args: Array[String]): Unit = {
/**
* 创建spark 环境
*/
val conf = new SparkConf()
.setAppName("Demo2UpdateStateByKey")
.setMaster("local[2]")
val sc: SparkContext = new SparkContext(conf)
/**
* 创建spark streaming环境
* 指定每隔多久计算一次
*/
val ssc: StreamingContext
= new StreamingContext(sc, Durations.seconds(5))
// 有状态算子需要设置checkpoint
// 用于保存之前的计算状态
ssc.checkpoint("Spark/data/checkpoint")
// 读取数据
val linesDS = ssc.socketTextStream("master", 8888)
val wordsDS = linesDS.flatMap(_.split(","))
val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))
// 有状态算子,可以预读被持久化的,之前滑动窗口的数据
/**
* 更新函数: 使用当前batch的数据去更新之前的计算结果,返回一个新的结果
*
* seq: 当前batch一个单词所有的value
* opt: 之前一个单词的计算结果(状态),
* 使用Option的原因是如果是第一次计算之前没有结果,那就是NOne
*/
val updateFun = (seq: Seq[Int], opt: Option[Int]) => {
//计算当前batch单词的数量
val currCount: Int = seq.sum
//获取之前的计算结果
val oldCount: Int = opt.getOrElse(0)
// 和即是最新的数量
val count: Int = currCount + oldCount
// 需要返回一个option
Option(count)
}
kvDS
.updateStateByKey(updateFun)
.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
3、foreachRDD
package streaming
import org.apache.spark.SparkContext
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Durations, StreamingContext}
object Demo3ToRDD {
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.master("local[2]")
.appName("Demo3ToRDD")
.getOrCreate()
import org.apache.spark.sql.functions._
import spark.implicits._
val sc: SparkContext = spark.sparkContext
val ssc
= new StreamingContext(sc, Durations.seconds(5))
val linesDS = ssc.socketTextStream("master", 8888)
/**
* foreachRDD: 将DStream转换成rdd
* 无返回值,等于是将每一个批处理
* 单独拿出来处理
*/
linesDS.foreachRDD(rdd => {
rdd
.flatMap(_.split(","))
.map((_, 1))
.reduceByKey(_ + _)
// 将rdd再转换成DataFrame
// 预先设置列名
val df: DataFrame = rdd.toDF("line")
// 注册成视图
df.createOrReplaceTempView("table")
df
.select(explode(split($"line", ",")) as "words")
.groupBy($"words")
.agg(count($"words") as "cnt")
.show()
})
// transform : 将DStream转换成rdd ,
// 在transform里面编写rdd的代码,写完之后需要返回一个新的rdd
val KVDS: DStream[(String, Int)] = linesDS.transform(rdd => {
val kv = rdd
.flatMap(_.split(","))
.map((_, 1))
.reduceByKey(_ + _)
kv
})
KVDS
.reduceByKey(_ + _)
.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
4、模拟带状态算子
很多暗坑需要注意,确实难,这个东西
package streaming
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.SparkContext
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.apache.spark.streaming.{Durations, StreamingContext}
object Demo4StreamOnRDD {
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.appName("Demo4StreamOnRDD")
.master("local[2]")
.config("spark.sql.shuffle.partitions", 1)
.getOrCreate()
import org.apache.spark.sql.functions._
import spark.implicits._
val sc: SparkContext = spark.sparkContext
val ssc: StreamingContext
= new StreamingContext(sc, Durations.seconds(5))
val linesDS
= ssc.socketTextStream("master", 8888)
linesDS.foreachRDD(rdd => {
val df: DataFrame = rdd.toDF("word")
val countDF: DataFrame = df
.select(explode(split($"word", ",")) as "word")
.groupBy($"word")
.agg(count($"word") as "cnt")
val path = "Spark/data/stream_count"
val conf = new Configuration()
val fs: FileSystem = FileSystem.get(conf)
/**
* 如果之前有结果,将当前batch的结果和之前的结果合并再保存
* 如果之前没有结果,直接保存当前的结果
*
* 此处是一个暗坑,如果使用一个目录进行读写
* 会导致在一次RDD中既有读,也有写,造成 socket 冲突,
* 为此,需要再开一个临时目录,进行缓冲
*/
if (fs.exists(new Path("Spark/data/stream_count"))) {
/**
* 读取上一次计算的结果
*/
val beforDF: DataFrame = spark
.read
.format("csv")
.schema("word String, cnt Long")
.load("Spark/data/stream_count")
// 合并这次读取的结果和上一次读取的结果
val allDF: DataFrame = beforDF
.union(countDF)
.groupBy($"word")
.agg(sum($"cnt") as "cnt")
// ;保存数据
allDF
.write
.mode(SaveMode.Overwrite)
.format("csv")
// 这里存到临时文件
.save("Spark/data/stream_count_temp")
// 这里是关键,对读写进行错开
// 先删除原文件,再将临时文件上位
fs.delete(new Path("Spark/data/stream_count"), true)
fs.rename(new Path("Spark/data/stream_count_temp"), new Path("Spark/data/stream_count"))
} else {
// 如果没有目录,说明是第一次
// 直接给他存起来
countDF
.write
.mode(SaveMode.Overwrite)
.format("csv")
.save("Spark/data/stream_count")
}
})
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
5、滑动窗口
package streaming
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object Demo5Window {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("Demo5Window")
.setMaster("local[2]")
val sc: SparkContext = new SparkContext(conf)
val ssc = new StreamingContext(sc, Durations.seconds(5))
ssc.checkpoint("Spark/data/checkpoint")
val linesDS: ReceiverInputDStream[String]
= ssc.socketTextStream("master", 8888)
val kvDS = linesDS
.flatMap(_.split(","))
.map((_, 1))
/**
* 统计最近15秒单词的数量,每隔5秒统计一次
*
*
* 窗口大小和滑动时间必须是batch时间的整数倍
*
*/
/*val countDS: DStream[(String, Int)]
= kvDS.reduceByKeyAndWindow(
(x: Int, y: Int) => x + y, //聚合函数
Durations.seconds(15), //窗口大小
Durations.seconds(5) //滑动时间
)*/
/**
* 如果窗口存在交叉的清空,会重复计算数据
* 所有可以对窗口计算进行优化,
*
*/
val cuntDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(
(x: Int, y: Int) => x + y, //聚合函数
// 这句是滑动窗口的一句优化
// 邻近窗口之间只是几个批量的差别
// 因此可以直接在上一个批量的结果中修改
(i: Int, j: Int) => i - j, //减去多余数据的函数
Durations.seconds(15), //窗口大小
Durations.seconds(5) //滑动时间
)
cuntDS
.filter(_._2 != 0)
.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
6、稽查布控
package streaming
import java.sql.{DriverManager, PreparedStatement, ResultSet}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable.ListBuffer
object Demo6JCBC {
// 稽查布控
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName("Demo6JC")
val sc: SparkContext = new SparkContext(conf)
val ssc: StreamingContext
= new StreamingContext(sc, Durations.seconds(5))
val linesDS = ssc.socketTextStream("master", 8888)
/**
* transform, foreachRDD
* 的外面属于Driver端,只执行一次
*
* transform, foreachRDD
* 的内部算子的外部属于Driver端,买一个batch都会执行一次
*
* 算子内属于Executor端,每一条数据执行一次
*/
val filterDS = linesDS.transform(rdd => {
Class.forName("com.mysql.jdbc.Driver")
val conn
= DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")
val ps: PreparedStatement
= conn.prepareStatement("select mdn from list")
val rs: ResultSet = ps.executeQuery()
val list = new ListBuffer[String]
while (rs.next()) {
val word = rs.getString("mdn")
list += word
}
conn.close()
println(s"布控列表:$list")
// 广播变量
val brodList = sc.broadcast(list)
val filterRDD = rdd.filter(line => {
val mdn = line.split(",")(0)
brodList.value.contains(mdn)
})
brodList.unpersist()
filterRDD
})
filterDS
filterDS.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}