Spark基础之3.0-实践

1. Spark常规作业

1.1 任务背景

对每天的产生的日志进行曝光,点击等行为的PV和UV的计算,同时需要区分新老用户,然后将不同的类别的PV和UV以一列的形式展示
原始日志:
userId, itemId, userType, action
处理完后需要不同天数统计结果,每个天数集合都是以下形式, 并将所有天数集合的数据放入同一个表格中:
new_click_pv, new_click_uv, old_click_pv, old_click_uv,
最后的结果
itemId, day1, day2, day3

1.2 解决方案

  1. 首先需要对每天的日志进行收集和统计,不过由于uv必须是统计时间范围内不重复的userId,因此,需要把指定时间范围的日志全部union出来之后才能进行统计, 时间通过filter函数来过滤,然后将不同时间段的数据union一下
  2. 获取数据后,按照itemId进行groupby,然后count(userId)计算pv值,但此时的count需要一些条件,比如action=‘click’,userType=‘new’
    count(when(col("userType") === "new" && col("action") === "expose", col("deviceId"))).cast("int").as("new_expose_pv"),
    以这种形式统计所有的行为,会增加列的数量
  3. 由于这一统计结果为某一个时间段的整体数据,需要做一个结构,统计为统一成为一个列,使用struct()将各列整合到一起
  4. 然后再加入一列时间 .withColumn("day_type", lit(date))
  5. 到这里,这个新的表格机会产生三列"book_id", "stats_day", "day_type"
  6. 仍然无法满足需求,没有做到让日期作为列名,因此需要做一个转置的操作.groupBy("book_id").pivot("day_type").agg(collect_set("stats_day")(0)).na.fill("")

pivot就是一个行列转换的方法.至此,解决

2. Spark Steaming作业

2.1 任务背景

通过实时统计Item在线上曝光次数,如果达到一个阈值,比如1000次,就更新推荐的召回队列,将没有曝光过得item进行曝光.
任务涉及的文件有:

  1. 一个包含全部item的文件, allItems.txt,里面有两列,itemid和category
  2. 一个实时的数据流
  3. 一个实时更新的线上item统计文件,包含两列,itemId和exposeTime
  4. 需要落盘两个文件,一个是持续的统计数据,一个是最新的召回队列(去除曝光次数大于阈值的item,加入新的item)

2.2 解决方案

  1. 这个流失作业的处理,需要每隔五分钟滑动一次,且滑动的步长是5,相当于一个滚动窗口,保证数据的不重复计算; 但是时间或许会随需求发生改变,需要将其作为一个参数传递,因此使用了case class

    
      case class TaskParams(duration: Int = 5, slide: Int = 5, maxExposePv: Int = 200,
          maxItemCount: Int = 10)
    
      object TaskParams {
    
        def apply(data: String): TaskParams = {
          val json = new JSONObject(data)
          TaskParams(
            duration = json.optInt("窗口大小"),
            slide = json.optInt("步长"),
            maxExposePv = json.optInt("最大曝光阈值"),
            maxItemCount = json.optInt("每次曝光的最大item数量"))
        }
      }
    
  2. 由于streaming需要去读取静态的allItems.txt,但是偶尔也会新增或者修改一部分Item,不能仅仅读取到就可以,需要每次处理流式数据是重新加载最新的文件;
    2.1 针对第一个问题,因为目前是调用的StreamingContext,如果使用这个上下文读的话,数据读取的格式是DStream的格式,另外就是无法做到实时能加载到最新的数据,因此调用的StreamingContext的上一层封装的SparkContext去读取数据,使用getOrCreate方法或者如果是一个StreamingContext的参数ssc的话,直接可以ssc.sparkContext.textFile获取
    2.2针对第二个问题,如何能保证每次读取的allItems.txt都是最新的呢,由于spark的惰性机制,只有在使用到的时候才会根据DAG图追溯计算,那么就可以将一个处理这个静态文件的action操作放到,流式数据处理的函数.foreachRDD中,如allItems.collectAsMap 这样就能保证每次获取的数据都是最近更新的数据.

  3. 将流式数据中的item做一个统计,统计曝光是一个持续的过程,从这个作业启动开始到解说应该是一直累加的过程,同时需要做到每隔一段时间做一次落盘数据,这时就需要一个函数.updateStateByKey,本案例中是每隔5分钟需要累加一下item的曝光次数,相当于一个词频统计,注意:在使用状态更新函数是,需要指定一个存放checkpoint的路径

    	//对每个窗口中的状态进行累加更新
      val updateFunc = (values: Seq[Int], state: Option[Int]) => {
      val currentCount = values.sum
      val previousCount = state.getOrElse(0)
      Some(currentCount + previousCount)
    }
    
    val updateFuncByKey = (iterator: Iterator[(String, Seq[Int], Option[Int])]) => {
      iterator.flatMap(t => updateFunc(t._2, t._3).map(s => (t._1, s)))
    }
     .updateStateByKey(updateFuncByKey, new HashPartitioner(ssc.sparkContext.defaultParallelism),  
     rememberPartitioner = true, initialRDD = ssc.sparkContext.emptyRDD[(String, Int)])
    
  4. 对于需要落盘的两个数据,均写在foreachRDD中即可

    .repartition(1) .saveAsTextFile("your path")
    
  5. 整体解决思路:
    5.1 创建case class传入参数;
    5.2 调用StreamingContext的sparkContext获取静态数据allItems.txt(因为一个作业不允许有两个上下文的存在),将读取的文件整理成RDD形式的(itemId, category)以备用,记为allItems
    5.3 获取流式数据stream, 在map中整理成自己需要的结构体
    5.4 调用窗口函数.window(Minutes(taskParams.duration), Minutes(taskParams.slide))
    5.5 调用状态更新函数.updateStateByKey(updateFuncByKey, new HashPartitioner(ssc.sparkContext.defaultParallelism), rememberPartitioner = true, initialRDD = ssc.sparkContext.emptyRDD[(String, Int)])
    5.5 调用.foreachRDD()将allItems进行action做操作allItemsId= allBooks.collectAsMap转为Map,用于过滤出包含在allItems中的item.流里面其他id不关注,
    5.6 生成召回队列newRecallData, 将allItemsId转为list之后进行map,如果在流中存在的itemId就记为已经更新的曝光次数,没有出现的(待出现的)记为0,用于生成新的召回队列
    5.7 按照类别category进行groupby之后按照曝光倒排,获取召回队列数据

6.整个过程可以放心使用savedAsText方法,不会报文件已经存在的异常.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值