Spark Streaming(WordCount、Window、ForEachRDD练习)

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 的外部数据库中。
注意:

  1. 连接不能写在 driver 层面(序列化)
  2. 如果写在 foreach 则每个 RDD 中的每一条数据都创建,得不偿失;
  3. 增加 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()
  }
}

猜你喜欢

转载自blog.csdn.net/Mogeko1/article/details/130531487