Spark Streaming笔记

目录

 

一、概念

二、Dstream入门

1、Dstream创建

2、RDD队列(了解)

3、根据端口号采集数据

4、自定义数据源

5、Kafka数据源(重点)

三、DStream转换

1、无状态转化操作

2、有状态转化操作(重点)

四、案例

1、WordCount案例实操(单次、累计)

2、把sparkstreaming获取到的数据存在redis里


一、概念

Spark Streaming用于流式数据的处理。Spark Streaming支持的数据输入源很多,例如:Kafka、Flume、Twitter、ZeroMQ和简单的TCP套接字等等。数据输入后可以用Spark的高度抽象原语如:map、reduce、join、window等进行运算。而结果也能保存在很多地方,如HDFS,数据库等。

和Spark基于RDD的概念很相似,Spark Streaming使用离散化流(discretized stream)作为抽象表示,叫作DStream。DStream 是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为 RDD 存在,而DStream是由这些RDD所组成的序列(因此得名“离散化”)。

二、Dstream入门

1、Dstream创建

(1)文件数据源

streamingContext.textFileStream(dataDirectory)

Spark Streaming 将会监控指定目录并不断处理移动进来的文件,目前不支持嵌套目录。

注意事项:

1)文件需要有相同的数据格式;

2)文件进入 dataDirectory的方式需要通过移动或者重命名来实现;

3)一旦文件移动进目录,则不能再修改,即便修改了也不会读取新数据;

案例:  (初步测试本地不成功)

原因: 这路径如果hdfs的路径 你直接hadoop fs -put
到你的监测路径就可以,如果是本地目录用file:///home/data
你不能移动文件到这个目录,必须用流的形式写入到这个目录形成文件才能被监测到。

import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.DStream

object FileStream {

  def main(args: Array[String]): Unit = {

    //1.初始化Spark配置信息
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("StreamWordCount")

    //2.初始化SparkStreamingContext
    val ssc: StreamingContext = new StreamingContext(sparkConf,Seconds(5))

    //3.监控文件夹创建DStream
    val dirStream: DStream[String] = ssc.textFileStream("hdfs://hdp-1:9000/spark")

    //4.将每一行数据做切分,形成一个个单词
    val wordStreams: DStream[String] = dirStream.flatMap(_.split(","))

    //5.将单词映射成元组(word,1)
    val wordAndOneStreams = wordStreams.map((_, 1))

    //6.将相同的单词次数做统计
    val wordAndCountStreams = wordAndOneStreams.reduceByKey(_ + _)

    //7.打印
    wordAndCountStreams.print()

    //8.启动SparkStreamingContext
    ssc.start()
    ssc.awaitTermination()
  }
}

SparkStreaming的输出方式:
1、print()         打印DStream中每个批次的前十个元素。用于开发调试。
2、saveAsTextFiles(prefix, [suffix])          保存为文本文件。每个批处理间隔的文件名基于前缀和后缀生成:“prefix-TIME_IN_MS [.suffix]”。
3、saveAsObjectFiles(prefix, [suffix])        将DStream的内容序列化为Java对象,并保存到SequenceFiles。每个批处理间隔的文件名基于前缀和后缀生成:“prefix-TIME_IN_MS [.suffix]”。
4、saveAsHadoopFiles(prefix, [suffix])      将DStream的内容保存为Hadoop文件。每个批处理间隔的文件名基于前缀和后缀生成:“prefix-TIME_IN_MS [.suffix]”。
5、foreachRDD(func) 最通用的输出方式,它将函数func应用于从流生成的每个RDD。此函数应将每个RDD中的数据推送到外部系统,例如将RDD保存到文件,或通过网络将其写入数据库。

2、RDD队列(了解)

用法及说明:使用ssc.queueStream(queueOfRDDs)创建DStream,每个推送到这个队列中的RDD,都会作为一个DStream处理。

案例:循环创建几个RDD,将RDD放入队列。通过SparkStream创建Dstream,计算WordCount

import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

import scala.collection.mutable
object RDDStream {
  def main(args: Array[String]) {

    //1.初始化Spark配置信息
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDDStream")

    //2.初始化SparkStreamingContext
    val ssc = new StreamingContext(conf, Seconds(4))

    //3.创建RDD队列
    val rddQueue = new mutable.Queue[RDD[Int]]()

    //4.创建QueueInputDStream
    val inputStream = ssc.queueStream(rddQueue,oneAtATime = false)

    //5.处理队列中的RDD数据
    val mappedStream = inputStream.map((_,1))
    val reducedStream = mappedStream.reduceByKey(_ + _)

    //6.打印结果
    reducedStream.print()

    //7.启动任务
    ssc.start()

//8.循环创建并向RDD队列中放入RDD
    for (i <- 1 to 5) {
      rddQueue += ssc.sparkContext.makeRDD(1 to 300, 10)
      Thread.sleep(2000)
    }
    ssc.awaitTermination()
  }
}

3、根据端口号采集数据

val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sparkConf, Seconds(10))   //每隔十秒刷新采集一次

/*创建文本输入流,并进行词频统计 reduceByKey 结果不累加*/
val lines = ssc.socketTextStream("hdp-1", 9999)

4、自定义数据源

继承Receiver,并实现onStart、onStop方法来自定义数据源采集。

自定义receiver

import java.io.{BufferedReader, InputStreamReader}
import java.net.Socket
import java.nio.charset.StandardCharsets
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.receiver.Receiver

class CustomerReceiver(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_ONLY) {

  //最初启动的时候,调用该方法,作用为:读数据并将数据发送给Spark
  override def onStart(): Unit = {
    new Thread("Socket Receiver") {     //线程可以一直存在,一直执行任务
      override def run() {
        receive()
      }
    }.start()
  }
  //读数据并将数据发送给Spark
  def receive(): Unit = {
    //创建一个Socket
    var socket: Socket = new Socket(host, port)
    //定义一个变量,用来接收端口传过来的数据
    var input: String = null
    //创建一个BufferedReader用于读取端口传来的数据
    val reader = new BufferedReader(new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8))
    //读取数据
    input = reader.readLine()
    //当receiver没有关闭并且输入数据不为空,则循环发送数据给Spark
    while (!isStopped() && input != null) {
      if("END".equals(input)){ //发送END就结束收集,此时发送数据不再收集,但是程序会一直执行
        return
      }else{
        store(input)     //store保存读取到的数据
        input = reader.readLine()
      }
    }
    //跳出循环则关闭资源
    reader.close()
    socket.close()
    //重启任务
    restart("restart")
  }
  override def onStop(): Unit = {}
}

在StreamingContext声明Receiver

object FileStreamTwo{
  def main(args: Array[String]): Unit = {
    //1.初始化Spark配置信息
    val sparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("StreamWordCount")
    //2.初始化SparkStreamingContext
    val ssc = new StreamingContext(sparkConf, Seconds(5))
    
    //3.创建自定义receiver的Streaming
    val lineStream = ssc.receiverStream(new CustomerReceiver("hdp-1", 9999))
    
    //4.将每一行数据做切分,形成一个个单词
    lineStream.flatMap(_.split(",")).map((_, 1)).reduceByKey(_ + _).print()
    //8.启动SparkStreamingContext
    ssc.start()
    ssc.awaitTermination()
  }
}

5、Kafka数据源(重点)

引入依赖:(有坑,切记版本与(scala、spark、kafka)环境对应,否则存在报错现象)

        <!--SparkStreaming-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>2.3.1</version>
<!--            <scope>provided</scope>-->        <!--  打包linux用 -->    
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-kafka_2.11</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.1.1</version>
        </dependency>
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import redis.clients.jedis.{Jedis}

object WithKafka {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setAppName("23").setMaster("local[*]")
    val ssc = new StreamingContext(sparkConf,Seconds(5))
    val kafkaDStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
      ssc,
      "hdp-1:2181", //zookeeper
      "dstream", //消费者组groupid
      Map("dstream" -> 3) //map中存放多个topic主题,格式为:<topic,分区数>
    )
//  tuple=>tuple._2.split(",")是因为kafka传过来数据是<k,v> k默认null,v是我们输入的值
    val value: DStream[String] = kafkaDStream.flatMap(tuple=>tuple._2.split(","))
//    value.map((_, 1)).reduceByKey(_ + _).print()
    val reduceDStream: DStream[(String, Int)] = value.map((_, 1)).reduceByKey(_ + _)

    reduceDStream.foreachRDD(rdd=>rdd.foreachPartition(r=>{
      var jedis: Jedis = null
        try {
          jedis = JedisPoolUtil.getConnection
        jedis.auth("123456")
        r.foreach(date => {
//hash <key,map>  map<String,String>    hincrBy(hashName,mapkey,mapvalue) Redis Hincrby 命令用于为哈希表中的字段值加上指定增量值。
          jedis.hincrBy("sparkstreaming", date._1, date._2)
        })
      }catch {
        case ex: Exception =>
          ex.printStackTrace()
      } finally {
        if (jedis != null) jedis.close()
      }
    }))
    ssc.start()
    ssc.awaitTermination()
  }
}

在linux运行kafka创建topic、启动生产者

 ./kafka-topics.sh --zookeeper hdp-1:2181 --create --topic dstream --partitions 3 --replication-factor 2

./kafka-console-producer.sh --broker-list hdp-1:9092 --topic dstrean

启动redis

 ./redis-server ../redis.conf 

注意点:reducebykey是无状态转移,只记录一个周期seconds统计的数据,redis中hincry是追加数据,所以也会实现累加

坑2:spark2.0有可能报错java.lang.NoClassDefFoundError: org/apache/spark/Logging错误

解决方案:在对应项目执行类的src/main/java下创建文件夹org.apache.spark,把logging jar包代码拷贝trait中

        streaming是自定义Sources文件夹,同java一样

三、DStream转换

1、无状态转化操作

2、有状态转化操作(重点)

updateStateByKey() 的结果会是一个新的 DStream,其内部的 RDD 序列是由每个时间区间对应的(键,状态)对组成的。

updateStateByKey操作使得我们可以在用新信息进行更新时保持任意的状态。为使用这个功能,你需要做下面两步: 

1. 定义状态,状态可以是一个任意的数据类型。 

2. 定义状态更新函数,用此函数阐明如何使用之前的状态和来自输入流的新值对状态进行更新。

使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态

1、updateStateByKey()

import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils

//TODO updateStateByKey实现累加wordcount,从kafka获取
object leijia {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setAppName("23").setMaster("local[*]")
    val ssc = new StreamingContext(sparkConf,Seconds(5))
//    ssc.checkpoint("check")
    ssc.sparkContext.setCheckpointDir("check")
    val kafkaDStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
      ssc,
      "hdp-1:2181", //zookeeper
      "dstream", //消费者组groupid
      Map("dstream" -> 3) //map中存放多个topic主题,格式为:<topic,分区数>
    )

    val value: DStream[String] = kafkaDStream.flatMap(tuple=>tuple._2.split(","))
    val maped: DStream[(String, Int)] = value.map((_, 1))
    val stateDStream: DStream[(String, Int)] = maped.updateStateByKey {
      case (seq, buffer) => {
        //seq是序列,即maped的value,即1 1 1 1 的序列,是旧的值,buffer是内存中的数据,getOrElse如果获取不到,默认是0,是本次周期采集的数据
        val sum: Int = buffer.getOrElse(0) + seq.sum
//        seq.foldLeft(0)(_ + _)  //0初始值, _+_ 累加
        Option(sum)   //Some(sum)   Option包括Some和None
      }
    }
    stateDStream.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

2、Window Operations

Window Operations可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming的允许状态。基于窗口的操作会在一个比 StreamingContext 的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。

关于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值。Note:默认情况下,这个操作使用Spark的默认数量并行任务(本地是2),在集群模式中依据配置属性(spark.default.parallelism)来做grouping。你可以通过设置可选参数numTasks来设置不同数量的tasks。

(5)reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]):这个函数是上述函数的更高效版本,每个窗口的reduce值都是通过用前一个窗的reduce值来递增计算。通过reduce进入到滑动窗口数据并”反向reduce”离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对keys的“加”“减”计数。通过前边介绍可以想到,这个函数只适用于”可逆的reduce函数”,也就是这些reduce函数有相应的”反reduce”函数(以参数invFunc形式传入)。如前述函数,reduce任务的数量通过可选参数来配置。注意:为了使用这个操作,检查点必须可用。 

(6)countByValueAndWindow(windowLength,slideInterval, [numTasks]):对(K,V)对的DStream调用,返回(K,Long)对的新DStream,其中每个key的值是其在滑动窗口中频率。如上,可配置reduce任务数量。

reduceByWindow() 和 reduceByKeyAndWindow() 让我们可以对每个窗口更高效地进行归约操作。它们接收一个归约函数,在整个窗口上执行,比如 +。除此以外,它们还有一种特殊形式,通过只考虑新进入窗口的数据和离开窗口的数据,让 Spark 增量计算归约结果。这种特殊形式需要提供归约函数的一个逆函数,比 如 + 对应的逆函数为 -。对于较大的窗口,提供逆函数可以大大提高执行效率

def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setAppName("23").setMaster("local[*]")
    val ssc = new StreamingContext(sparkConf,Seconds(3))

    val kafkaDStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
      ssc,
      "hdp-1:2181", //zookeeper
      "dstream", //消费者组groupid
      Map("dstream" -> 3) //map中存放多个topic主题,格式为:<topic,分区数>
    )
    //windows(windowLength, slideInterval) 
    // 窗口大小,滑动步长(都是周期整数倍) 即3个周期数据,一次移动一个周期
    val windowed: DStream[(String, String)] = kafkaDStream.window(Seconds(9),Seconds(3))
    windowed.flatMap(t=>(t._2.split(" "))).map((_,1)).reduceByKey(_+_).print()

    ssc.start()
    ssc.awaitTermination()
  }

kafka0.10版本util方式消费kafka,且windows方法会报序列化错误,需要优化将ConsumerRecord类注册为使用Kyro序列化

object windows {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setAppName("23").setMaster("local[*]")
      .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") //将ConsumerRecord类注册为使用Kyro序列化
    val ssc = new StreamingContext(sparkConf,Seconds(3))

    val inputDstream: InputDStream[ConsumerRecord[String, String]] = MyKafkaUtil.getKafkaStream("window", ssc)

    //windows(windowLength, slideInterval)
    // 窗口大小,滑动步长(都是周期整数倍) 即3个周期数据,一次移动一个周期
    val windowed: DStream[ConsumerRecord[String, String]] = inputDstream.window(Seconds(9),Seconds(3))
    windowed.map(t=>t.value()).map((_,1)).reduceByKey(_+_).print()

//    inputDstream.map(t=>t.value()).map((_,1)).reduceByKey(_+_).print()

    ssc.start()
    ssc.awaitTermination()
  }
}
package com.bgd.realtime.util

import org.apache.kafka.common.serialization.StringDeserializer
import java.util.Properties

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}

object MyKafkaUtil {
  private val properties: Properties = PropertiesUtil.load("config.properties")
  val broker_list = properties.getProperty("kafka.broker.list")

  // kafka消费者配置
  val kafkaParam = Map(
    "bootstrap.servers" -> broker_list,//用于初始化链接到集群的地址
    "key.deserializer" -> classOf[StringDeserializer],
    "value.deserializer" -> classOf[StringDeserializer],
    //用于标识这个消费者属于哪个消费团体
    "group.id" -> "gmall_consumer_group",
    //如果没有初始化偏移量或者当前的偏移量不存在任何服务器上,可以使用这个配置属性
    //可以使用这个配置,latest自动重置偏移量为最新的偏移量
    "auto.offset.reset" -> "latest",
    //如果是true,则这个消费者的偏移量会在后台自动提交,但是kafka宕机容易丢失数据
    //如果是false,会需要手动维护kafka偏移量
    "enable.auto.commit" -> (true: java.lang.Boolean)
  )

  // 创建DStream,返回接收到的输入数据
  // LocationStrategies:根据给定的主题和集群地址创建consumer
  // LocationStrategies.PreferConsistent:持续的在所有Executor之间分配分区
  // ConsumerStrategies:选择如何在Driver和Executor上创建和配置Kafka Consumer
  // ConsumerStrategies.Subscribe:订阅一系列主题

  def getKafkaStream(topic: String,ssc:StreamingContext): InputDStream[ConsumerRecord[String,String]]={
    val dStream = KafkaUtils.createDirectStream[String,String](ssc, LocationStrategies.PreferConsistent,ConsumerStrategies.Subscribe[String,String](Array(topic),kafkaParam))
    dStream
  }
}

3、Join

连接操作(leftOuterJoin, rightOuterJoin, fullOuterJoin也可以),可以连接Stream-Stream,windows-stream to windows-stream、stream-dataset、Stream-Stream Joins

val stream1: DStream[String, String] = ...
val stream2: DStream[String, String] = ...
val joinedStream = stream1.join(stream2)

val windowedStream1 = stream1.window(Seconds(20))
val windowedStream2 = stream2.window(Minutes(1))
val joinedStream = windowedStream1.join(windowedStream2)
Stream-dataset joins
val dataset: RDD[String, String] = ...
val windowedStream = stream.window(Seconds(20))...
val joinedStream = windowedStream.transform { rdd => rdd.join(dataset) }

4、Transform

Transform原语允许DStream上执行任意的RDD-to-RDD函数。即使这些函数并没有在DStream的API中暴露出来,通过该函数可以方便的扩展Spark API。该函数每一批次调度一次。其实也就是对DStream中的RDD应用转换。

比如下面的例子,在进行单词统计的时候,想要过滤掉spam的信息。

val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam information

val cleanedDStream = wordCounts.transform { rdd =>
  rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning
  ...
}

四、案例

1、WordCount案例实操(单次、累计)

  需求:使用netcat工具向9999端口不断的发送数据,通过SparkStreaming读取端口数据并统计不同单词出现的次数

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.11</artifactId>
    <version>2.1.1</version>
</dependency>

//单次统计:只处理实时发过来的数据,之前的数据不统计

import org.apache.spark.{HashPartitioner, SparkConf}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object NetworkWordCount {
  def main(args: Array[String]) {
    val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[2]")
    val ssc = new StreamingContext(sparkConf, Seconds(10))   //每隔十秒刷新采集一次

    /*创建文本输入流,并进行词频统计 reduceByKey 结果不累加*/
    val lines = ssc.socketTextStream("hdp-1", 9999)
    lines.flatMap(_.split(" ")).map(x => (x, 1)).reduceByKey(_ + _).print()

    /*启动服务*/
    ssc.start()
    /*等待服务结束*/
    ssc.awaitTermination()
  }
}

启动程序并通过NetCat发送数据:

[xin@hdp-1 spark]$ nc -lk 9999
hello xin

//累计统计   之前的单词也统计

object NetworkUpdateStateWordCount {
  /**
   * String : 单词 hello
   * Seq[Int] :单词在当前批次出现的次数
   * Option[Int] : 历史结果
   */
  //    val i : Int = 11;
  val updateFunc = (iter: Iterator[(String, Seq[Int], Option[Int])]) => {
    //iter.flatMap(it=>Some(it._2.sum + it._3.getOrElse(0)).map(x=>(it._1,x)))
    iter.flatMap{case(x,y,z)=>Some(y.sum + z.getOrElse(0)).map(m=>(x, m))}
  }

  def main(args: Array[String]) {
    //    LoggerLevel.setStreamingLogLevels()
    val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkUpdateStateWordCount")
    val ssc = new StreamingContext(conf, Seconds(5))
    //做checkpoint 写入共享存储中
    ssc.checkpoint("E:/hadoop/hdpdata/checkpoint")
    val lines = ssc.socketTextStream("hdp-1", 9999)
    //val result = lines.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_)
    //updateStateByKey结果可以累加但是需要传入一个自定义的累加函数:updateFunc
    val results = lines.flatMap(_.split(" ")).map((_,1)).updateStateByKey(updateFunc, new HashPartitioner(ssc.sparkContext.defaultParallelism), true)
    results.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

2、把sparkstreaming获取到的数据存在redis里

class AndRdis {
}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
import redis.clients.jedis.Jedis

object NetworkWordCountToRedis {
  def main(args: Array[String]) {
    val sparkConf = new SparkConf().setAppName("NetworkWordCountToRedis").setMaster("local[*]")
    val ssc = new StreamingContext(sparkConf, Seconds(5))

    /*创建文本输入流,并进行词频统计*/
    val lines = ssc.socketTextStream("hdp-1", 9999)
    val pairs: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map(x => (x, 1)).reduceByKey(_ + _)
    /*保存数据到Redis*/
    pairs.foreachRDD { rdd =>        //foreachRDD遍历DStream里面的RDD
      rdd.foreachPartition { partitionOfRecords =>    //遍历每个分区
        var jedis: Jedis = null
        try {
          jedis = JedisPoolUtil.getConnection     //获得jedis对象
          jedis.auth("123456")
          //hash <key,map>  map<String,String>    hincrBy(hashName,mapkey,mapvalue) Redis Hincrby 命令用于为哈希表中的字段值加上指定增量值。
          partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2)) 
        } catch {
          case ex: Exception =>
            ex.printStackTrace()
        } finally {
          if (jedis != null) jedis.close()
        }
      }
    }
    ssc.start()
    ssc.awaitTermination()
  }
}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolUtil {
    /* 声明为volatile防止指令重排序 */
    private static volatile JedisPool jedisPool = null;
    private static final String HOST = "hdp-1";
    private static final int PORT = 6379;

    /* 双重检查锁实现懒汉式单例 */
    public static Jedis getConnection() {
        if (jedisPool == null) {
            synchronized (JedisPoolUtil.class) {
                if (jedisPool == null) {
                    JedisPoolConfig config = new JedisPoolConfig();
                    config.setMaxTotal(30);
                    config.setMaxIdle(10);
                    jedisPool = new JedisPool(config, HOST, PORT);
                }
            }
        }
        return jedisPool.getResource();
    }
}
发布了77 篇原创文章 · 获赞 19 · 访问量 4061

猜你喜欢

转载自blog.csdn.net/qq_41861558/article/details/103182446
今日推荐