Spark Streaming WordCount
使用 netcat 工具向 8888 端口不断的发送数据,通过 SparkStreaming 读取端口数据并
统计不同单词出现的次数
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{
Seconds, StreamingContext}
object SparkStreamingDemo1 {
def main(args: Array[String]): Unit = {
//初始化 Spark 配置信息
val conf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getName)
//初始化 SparkStreamingContext
val ssc = new StreamingContext(conf, Seconds(3))
//设置日志类型
ssc.sparkContext.setLogLevel("error")
//设置检查点
ssc.checkpoint("file:///F:/JavaTest/SparkIPMappingDemo/checkpoint")
//通过监控端口创建 DStream
val lines = ssc.socketTextStream("192.168.58.203", 8888)
//将每一行数据做切分,形成一个个单词 读进来的数据为一行行 map 将单词映射成元组
val wordAndOne = lines.flatMap(_.split(" ")).map((_, 1))
//将相同的单词次数做统计
//val wordAndCountStreams = wordAndOneStreams.reduceByKey(_+_) 或者
val valuse: DStream[(String, Int)] = wordAndOne.updateStateByKey((newValue: Seq[Int], buffer: Option[Int]) => {
val result = newValue.sum + buffer.getOrElse(0)
Some(result)
})
//打印
valuse.print()
//linux("192.168.58.203)>nc -lk 8888 (随便写入些东西)
//启动 SparkStreamingContext
ssc.start()
ssc.awaitTermination()
}
}
Spark Streaming Window
Window Operations 可以设置窗口的大小和滑动窗口的间隔来动态的获取当前 Steaming 的允许状态。所有基于窗口的操作都需要两个参数,分别为窗口时长以及滑动步长。
1.窗口时长:计算内容的时间范围;
2.滑动步长:隔多久触发一次计算
注意:这两者都必须为采集周期大小的整数倍
package SparkTest.SparkStreaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{
Seconds, StreamingContext}
object WindowDemo {
def main(args: Array[String]): Unit = {
//初始化 Spark 配置信息
val conf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getName)
//初始化 SparkStreamingContext
val ssc = new StreamingContext(conf, Seconds(3))
//设置日志类型
ssc.sparkContext.setLogLevel("error")
//设置检查点
ssc.checkpoint("file:///F:/JavaTest/SparkIPMappingDemo/checkpoint")
//通过监控端口创建 DStream
val lines = ssc.socketTextStream("192.168.58.203", 8888)
将每一行数据做切分,形成一个个单词 map 将单词映射成元组
val wordAndOne = lines.flatMap(_.split(" ")).map((_, 1))
val result = wordAndOne.reduceByKeyAndWindow((x: Int, y: Int) => {
x + y
}, Seconds(9), Seconds(6)) //计算范围9 滑动窗口步长6
//或者
// val result = wordAndOne.reduceByKeyAndWindow((total: Int, enter: Int) => {
// total + enter
// }, (total: Int, leave: Int) => {
// total - leave
// }, Seconds(9), Seconds(6))
//linux("192.168.58.203)>nc -lk 8888 (随便写入些东西)
result.print()
ssc.start()
ssc.awaitTermination()
}
}
关于 Window 的操作还有如下方法:
(1)window(windowLength, slideInterval): 基于对源 DStream 窗化的批次进行计算返回一个新的 Dstream;
(2)countByWindow(windowLength, slideInterval): 返回一个滑动窗口计数流中的元素个数;
(3)reduceByWindow(func, windowLength, slideInterval): 通过使用自定义函数整合滑动区间流元素来创建一个新的单元素流;
(4)reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]): 当在一个(K,V)对的 DStream 上调用此函数,会返回一个新(K,V)对的 DStream,此处通过对滑动窗口中批次数据使用 reduce 函数来整合每个 key 的 value 值。
(5)reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]): 这个函数是上述函数的变化版本,每个窗口的 reduce 值都是通过用前一个窗的 reduce 值来递增计算。通过 reduce 进入到滑动窗口数据并”反向 reduce”离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对 keys 的“加”“减”计数。通过前边介绍可以想到,这个函数只适用于”可逆的 reduce 函数”,也就是这些 reduce 函数有相应的”反 reduce”函数(以参数 invFunc 形式传入)。如前述函数,reduce 任务的数量通过可选参数来配置。
Spark Streaming ForEachRDD
foreachRDD(func):这是最通用的输出操作,即将函数 func 用于产生于 stream 的每一个RDD。其中参数传入的函数 func 应该实现将每一个 RDD 中数据推送到外部系统,如将RDD 存入文件或者通过网络将其写入数据库。通用的输出操作 foreachRDD(),它用来对 DStream 中的 RDD 运行任意计算。这和 transform() 有些类似,都可以让我们访问任意 RDD。在 foreachRDD()中,可以重用我们在 Spark 中实现的所有行动操作。比如,常见的用例之一是把数据写到诸如 MySQL 的外部数据库中。
注意:
- 连接不能写在 driver 层面(序列化)
- 如果写在 foreach 则每个 RDD 中的每一条数据都创建,得不偿失;
- 增加 foreachPartition,在分区创建(获取)。
dstream.foreachRDD {
rdd =>
rdd.foreachPartition {
partitionOfRecords =>
// ConnectionPool is a static, lazily initialized pool of connections
val connection = ConnectionPool.getConnection()
partitionOfRecords.foreach(record => connection.send(record))
ConnectionPool.returnConnection(connection) // return to the pool for future reuse
}
}
package SparkTest.SparkStreaming
import java.sql.PreparedStatement
import SparkTest.util.{
DBUtil, OffsetUtils}
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{
Durations, StreamingContext}
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.{
HasOffsetRanges, KafkaUtils, OffsetRange}
object SparkstreamingKafkaSaveToMysqlDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local[*]")
conf.setAppName(this.getClass.getSimpleName)
val ssc = new StreamingContext(conf, Durations.seconds(5))
//设置日志级别
ssc.sparkContext.setLogLevel("Error")
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "192.168.58.201:9092,192.168.58.202:9092,192.168.58.203:9092",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "demo3", //
/**
* 当没有初始的offset,或者当前的offset不存在,如何处理数据
* earliest :自动重置偏移量为最小偏移量
* latest:自动重置偏移量为最大偏移量【默认】
* none:没有找到以前的offset,抛出异常
*/
"auto.offset.reset" -> "latest",
"enable.auto.commit" -> (false: java.lang.Boolean)
/**
* 当设置 enable.auto.commit为false时,不会自动向kafka中保存消费者offset.需要异步的处理完数据之后手动提交
*/
// "enable.auto.commit" -> (true: java.lang.Boolean) //默认是true 每5s提交一次
)
val topics = Array("sparktest")
// val offset = Map.empty[TopicPartition, Long]
// val offset = OffsetUtils.queryHistoryOffsetFromRedis()
val offset=OffsetUtils.queryHistoryOffsetFromMysql();
// val offset = Map.empty[TopicPartition, Long]
// 原始的stream 中包含了 offset的信息
val kafkaDstream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
PreferConsistent,
Subscribe[String, String](topics, kafkaParams,offset)
)
kafkaDstream.foreachRDD(rdd => {
if (!rdd.isEmpty()) {
var offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
val reduced = rdd.map(_.value()).flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
val result = reduced.collect()
println(result.toBuffer)
val con = DBUtil.getConnection()
var pstmt1: PreparedStatement = null
var pstmt2: PreparedStatement = null
try {
//取消自动提交,默认jdbc 一条语句一个事务
con.setAutoCommit(false)
/*
result.foreach(tuple => {
pstmt1 = con.prepareStatement("INSERT INTO wordcount(word,counts) VALUES(?,?) ON DUPLICATE KEY UPDATE counts=counts+?;")
pstmt1.setString(1, tuple._1)
pstmt1.setInt(2, tuple._2)
pstmt1.setInt(3, tuple._2)
pstmt1.addBatch()
})
pstmt1.executeBatch()
*/
for (tuple <- result) {
pstmt1 = con.prepareStatement("INSERT INTO wordcount(word,counts) VALUES(?,?) ON DUPLICATE KEY UPDATE counts=counts+?;")
pstmt1.setString(1, tuple._1)
pstmt1.setInt(2, tuple._2)
pstmt1.setInt(3, tuple._2)
pstmt1.executeUpdate()
}
for (offsetRange <- offsetRanges) {
val topic = offsetRange.topic
val partition = offsetRange.partition
val untilOffset = offsetRange.untilOffset
//create table offset (app_gid varchar(30) ,topic_partition varchar(30),offset int(11));
pstmt2 = con.prepareStatement("insert into offset (app_gid,topic_partition,offset) values (?,?,?) on DUPLICATE KEY UPDATE offset=?")
pstmt2.setString(1, "test_demo1")
pstmt2.setString(2, topic + "-" + partition)
pstmt2.setInt(3, untilOffset.toInt)
pstmt2.setInt(4, untilOffset.toInt)
pstmt2.executeUpdate()
}
con.commit()
} catch {
case e: Exception => {
con.rollback()
ssc.stop()
e.printStackTrace()
}
} finally {
if (pstmt1 != null) {
pstmt1.close()
}
if (pstmt2 != null) {
pstmt2.close()
}
if (con != null) {
con.close()
}
}
}
})
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}