模拟实时区域订单金额统计:SparkStreaming从Kafka中读取数据,设置检查点,处理数据后,并将结果存到Redis中,并实现执行优化

实现思路

  1. 第一步获取StreamingContext对象,因为要使用检查点恢复数据,所以不能使用new StreamingContext的方法获取对象,要使用StreamingContext.getOrCreate建立对象
  2. 创建StreamingContext对象,使用了贷出模式 ——贷出函数的方式来创建
  3. 从Kafka的生产者端读取数据进行分析
  4. 读取数据的方式采用Direct方式读取数据
  5. 处理读取到的数据,获取需要的值
  6. 调用updateStateByKey实时累加统计函数,进行累加统计
  7. 将结果打印到控制台,并将数据存放到Redis中

具体代码实现

import com.huadian.bigdata.jedis.JedisPoolUtil
import kafka.serializer.StringDecoder
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

object G_RedisOrderTotalStreaming {
  //检查点目录
  val CHECK_POINT_PATH = "file:///E:\\JavaWork\\20190811\\test5"
  //Redis使用Hash存放数据
  val REDIS_KEY_ORDER_TOTAL_PRICE = "orders:total:price"
  def main(args: Array[String]): Unit = {
    /**
      * 先去查看检查点,
      * createFunc:第一次运行SparkStreaming应用的时候才会执行,才会创建StreamingContext
      */
    val ssc: StreamingContext = StreamingContext.getOrCreate(
      CHECK_POINT_PATH,
      createFunc
    )
    //设置日志级别
    ssc.sparkContext.setLogLevel("WARN")
    ssc.start()
    ssc.awaitTermination()
    //stop(stopSparkContext: Boolean, stopGracefully: Boolean)
    ssc.stop(true,true)
  }

  /**
    * 从源端读取数据 然后进行分析
    *   类似于贷出模式 用户函数
    * @param ssc
    * @return
    */
  def processData(ssc:StreamingContext):Unit={
    val kafkaParams: Map[String, String] = Map(
      "metadata.broker.list"->"bigdata-hpsk01.huadian.com:9092,bigdata-hpsk01.huadian.com:9093,bigdata-hpsk01.huadian.com:9094",
      "auto.offset.reset"->"largest" //读取最新数据
    )
    val topics: Set[String] = Set("orderTopic")

    val inputDStream: DStream[String] = KafkaUtils
      .createDirectStream[String, String, StringDecoder,StringDecoder](
      ssc,
      kafkaParams,
      topics
    ).map(_._2) //只需要获取Topic中每条Message中Value的值

    val orderDStream: DStream[(Int, Double)] = inputDStream
      //.filter(line => line.trim.length >0 && line.trim.split(",").length ==3)
      .transform(rdd=>{
      rdd
        //过滤不合法的数据
        .filter(line => line.trim.length >0 && line.trim.split(",").length ==3)
        //提取字段
        .map(line =>{
        val splits = line.split(",")
        (splits(1).toInt,splits(2).toDouble)
      })
    })

    /**实时累加统计函数
      * def updateStateByKey[S: ClassTag](
      * updateFunc: (Seq[V],     表示当前批次中,相同Key的所有value集合
      * Option[S]   历史状态,历史没有这个可以,所有Option类型
      * ) => Option[S] 这是返回值的类型,返回值是Option,比如数据过滤的话,数据没了
      * ): DStream[(K, S)]
      *
      *updateStateByKey和reduce函数非常非常类似
      */
    val pirceDstream: DStream[(Int, Double)] = orderDStream.updateStateByKey(
      (values:Seq[Double],state:Option[Double])=>{
        //1、获取以前状态的值
        val previousState = state.getOrElse(0.0)
        //2、获取当前批次中状态数据
        val currentState = values.sum
        //3、更新状态发挥
        Some(previousState + currentState)
      })


     //结果输出
    pirceDstream.print()

    //结果存放到Redis中
    pirceDstream.foreachRDD(rdd=>{
      if(!rdd.isEmpty()){
       //减少分区数目
        rdd.coalesce(1).foreachPartition(iter=>{
        //自定义Redis连接类,使用了Jedis连接了Redis数据库
          val jedis = JedisPoolUtil.getJedisPoolInstance.getResource
          iter.foreach{
            case(provinceid,orderTotal)=>{
              jedis.hset(REDIS_KEY_ORDER_TOTAL_PRICE,
                provinceid.toString,
                orderTotal.toString)

            }
          }
          JedisPoolUtil.release(jedis)
        })
      }
    })
  }

  /**
    * 创建StreamingContext对象
    *   类似于贷出模式 贷出函数
    * @return
    */
  def createFunc():StreamingContext={
    val conf = new SparkConf()
      .setMaster("local[3]") //为什么启动3个,有一个Thread运行Receiver
      .setAppName("F_CheckPointOrderTotalStreaming")
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
    //日志级别
    ssc.sparkContext.setLogLevel("WARN")

    processData(ssc)
    ssc.checkpoint(CHECK_POINT_PATH)
    ssc
  }
}

代码优化

上面的代码中,会将所有的结果都输出
即没有输入数据时,也会输出原来的处理结果
可以对其进行优化,即输出有数据更新的结果
删除原有的累加代码块

 /**
        * todo:
        *   下面函数,针对每一条数据进行更新状态,
        *   并不是将同一批次相同的Key的value聚合在一起更新状态
        * def function[KeyType, ValueType, StateType, MappedType](
        * mappingFunction: (KeyType, Option[ValueType], State[StateType]) => MappedType
        *
        * KeyType:key的类型
        * StateType:表示 状态的类型,此时来说,就是销售营业额
        * ):
        */

      val mappingFunc = (provinceId:Int,orderPrice:Option[Double],state:State[Double])=>{
        //获取以前状态的值
        val previouState = state.getOption().getOrElse(0.0)

        //更新的状态
        val lastestState = previouState + orderPrice.getOrElse(0.0)

        state.update(lastestState)

        //MappedType :最终获取的 结果的类型 ,就是DSTream状态统计输出RDD的类型
        (provinceId,lastestState)
      }

      val pirceDstream: MapWithStateDStream[Int, Double, Double, (Int, Double)] =  orderDStream.mapWithState(StateSpec.function(mappingFunc))
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无名一小卒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值