Spark之用户连续登陆案例和流量统计案例分析-算子联用(6)

案例一 : 计算出连续登陆三天的用户

重要的算子 : repartitionAndSortWithinPartitions方法 ,分区并且在区里面进行排序

数据如下 : 

guid01,2018-02-28
guid01,2018-03-01
guid01,2018-03-05
guid01,2018-03-02
guid01,2018-03-04
guid01,2018-03-06
guid01,2018-03-07
guid02,2018-03-01
guid02,2018-03-03
guid02,2018-03-02
guid02,2018-03-06

1  第一种方案

大概思路,具体步骤以代码块中描述的为准 :

1) 创建 SparkContext ,获得sc, 然后通过sc 创建原始的RDD ,获得文件数据,对数据进行切割处理,组装成元组,去重(map,split,distinct)

2) 按照元组的key进行分组 groupByKey

3) 将同一个组的value展开 ,按照value中的数值进行排序(因为执行的都是窄依赖的Transformation,所以可以将其合并到一个函数中)  flatMapValue  sortBy  map  Calender(日历类)  SimpleDateFormat(日期格式化类)

4) 对上一步得到的数据进行再组装 map

5) 按照组装数据的key进行分组 ,value值进行聚合  reduceByKey   Ordering[String].min/max--取最小值到最大值进行聚合

6) 对得到的数据进行再组装 ,将需要的数据留下,然后过滤掉不符合需求的数据.  map  filter

import java.text.SimpleDateFormat
import java.util.Calendar
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object ContinueLogin011 {
  def main(args: Array[String]): Unit = {
    --1 在main方法中输入参数决定是否在本地运行,布尔类型
    val isLocal = args(0).toBoolean
    --2 获取 conf
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
    --3 如果main方法中index为0的参数输入的是true,就表示在本地运行
    if(isLocal){
      conf.setMaster("local[*]")
    }
    --4 创建sparkcontext
    val sc = new SparkContext(conf)
    --5 创建原始的RDD ,在main方法参数index为1的位置输入待处理文件的输入路径
    val lines: RDD[String] = sc.textFile(args(1))
    --6 对读取的一行行文件进行处理 ,切割,组装 ,按照组装的整体进行去重
    val uidAndDate: RDD[(String, String)] = lines.map(line => {
      val strings = line.split(",")
      val uid = strings(0)
      val date = strings(1)
      (uid, date)
    }).distinct()

    --7 先分组 ,key是 uid ,value 是对应的日期字符串,但是日期字符串是没有排序的,是混乱的,下一步需要排序
    val groupde: RDD[(String, Iterable[String])] = uidAndDate.groupByKey()
    --println(groupde.collect().toBuffer) (guid01,CompactBuffer(2018-03-05, 2018-03-01, 2018-03-07..  (guid02,CompactBuffer(2018-03-06, 2018-03-01...

    --8 然后在组内按日期的升序进行排序 ,数据进过一定的处理后 ,最后得到的是
    --(uid,(date,rowNumber,diffDate))
    val uidAndDateNumDiffDate: RDD[(String, (String, Int, String))] = groupde.flatMapValues(it => {
      --8.1 定义日历类 ,需要将日期字符串转为日期类型,方便后续对日期进行相减的操作
      val calendar = Calendar.getInstance()
      --8.2 定义日期格式类型
      val sdf = new SimpleDateFormat("yyyy-MM-dd")
      --8.3 对组的value值进行排序,因为这个数据不是很大,不用担心会内存溢出,所以选择将迭代器转list
      --得到排好序的日期,保存在list集合里面
      val sorted: List[String] = it.toList.sortBy(x => x)
      --8.4 定义一个行号
      var rowNumber = 0
      --8.4 对排好序的日期进行操作,需要对其进行遍历
      sorted.map(date => {
        --8.5 遍历一次行号加1
        rowNumber += 1
        --8.6 将日期字符串转为定义的日期规定的格式
        val dt = sdf.parse(date)
        --8.7 然后将转了规定格式的日期放到日历类中
        calendar.setTime(dt)
        --8.8 然后可以对指定的日历类的年月日单位进行加减操作 ,这里需要减去行号,也就是需要减去一天
        calendar.add(Calendar.DATE, -rowNumber)
        --8.9 因为需要的是日期字符串,所以这里需要将处理的差值进转换回来
        val time = calendar.getTime
        val difDate = sdf.format(time)
        --想要得到的数据是 date 行号 差值
        (date, rowNumber, difDate)
      })
    })

   --println(uidAndDateNumDiffDate.collect().toBuffer)  //(uid,(date,rowNumber,diffDate))
    --结果 : ArrayBuffer((guid01,(2018-02-28,1,2018-02-27)), (guid01,(2018-03-01,2,2018-02-27)), (guid01,(2018-03-02,3,2018-02-27)),
    --(guid01,(2018-03-04,4,2018-02-28)), (guid01,(2018-03-05,5,2018-02-28)),
    --(guid01,(2018-03-06,6,2018-02-28)), (guid01,(2018-03-07,7,2018-02-28)),
    --(guid02,(2018-03-01,1,2018-02-28)), (guid02,(2018-03-02,2,2018-02-28)), (guid02,(2018-03-03,3,2018-02-28)), (guid02,(2018-03-06,4,2018-03-02)))

    --需求是计算出类型登录3天及以上的起始时间和结束时间
    --9 需要对之前得到的数据进行再组装处理,所以需要先遍历,需要对uid和diffDate进行分组聚合
    --                           (uid,diffDate),(date,null,1)                                              (uid,diffDate),(date,null,1)
    val uidDiffDateAndDate: RDD[((String, String), (String,String, Int))] = uidAndDateNumDiffDate.map(t => ((t._1, t._2._3), (t._2._1, null, 1)))
    --10 这里的t1和t2是同一个(uid,diffDate) 组不同的value ,
    --需要得到((uid,diffDate),(startDate,endDate,count))
    val uiddiffDateAndStarDtEndDt: RDD[((String, String), (String, String, Int))] = uidDiffDateAndDate.reduceByKey((t1, t2) => {
      --11 获取(uid,diffDate)最小的date(起始时间) ,获取(uid,diffDate)最大的date(结束时间),然后对第三个元素进行相加
      --获取最大值和最小值时的数值类型为 "整数类型(Int/Long)" ,用 Math 关键字: Math.min/Math.max
      --获取最大值和最小值时的数值类型为 "字符串类型(String)" ,用 Ordering 关键字: Ordering[String].min/Ordering[String].max
      (Ordering[String].min(t1._1, t2._1), Ordering[String].max(t1._1, t2._1), t1._3 + t2._3)
    })

  --println(value.collect().toBuffer)
    --结果为 : ArrayBuffer(((guid01,2018-02-27),(2018-02-28,2018-03-02,3)), ((guid02,2018-02-28),(2018-03-01,2018-03-03,3)),
    --((guid01,2018-02-28),(2018-03-04,2018-03-07,4)),
    --((guid02,2018-03-02),(2018-03-06,null,1)))
    --12 将里面的元素进行遍历再组装 ,得到想要的形式:(uid,startDate,endDate,count) ,然后对count进行过滤
    val result: RDD[(String, String, String, Int)] = uiddiffDateAndStarDtEndDt.map(t => (t._1._1, t._2._1, t._2._2, t._2._3)).filter(_._4 >= 3)

    println(result.collect().toBuffer)
    --结果为 : ArrayBuffer((guid01,2018-02-28,2018-03-02,3),
    --(guid02,2018-03-01,2018-03-03,3),
    --((guid01,2018-03-04,2018-03-07,4))
    sc.stop()
  }
}

2  第二种方案

大概思路,具体步骤以代码块中描述的为准 :

1) 创建 SparkContext ,获得sc, 然后通过sc 创建原始的RDD ,获得文件数据,对数据进行切割处理,组装成元组,去重(map,split,distinct)

2) 创建分区器自定义一个分区规则,想要一个用户一个分区 ,就需要得到用户的个数(一个用户不同时间登录就有多条数据,需要对其进行去重) map distinct  collect

3) class类创建自定义分区器继承Partitioner,一个用户对应一个唯一的分区号,第一一个用可变的mutable.HashMap装用户和对应的分区号,分区器需要重写两个方法,numPartition就是指分区的个数 , getPartition是将用户从key中取出 ,然后根据用户名,到HashMap中得到对应的分区号

4) object类中new创建的class类,并将所有用户都放进去,每一个用户都能得到一个自己的分区号,返回一个对象

5) 然后这个对象调用rePartitionAndSortWithInPartition ,分区然后在区内进行排序,前提是根据key进行的分区排序操作

6) 得到一个区的数据/迭代器,然后对迭代器的每一条数据进行处理(因为执行的都是窄依赖的Transformation,所以可以将其合并到一个函数中)  mapPartition  map  ,给每一行加上唯一的行号 ,Calender(日历类)  SimpleDateFormat(日期格式化类) ,方便给日期做加减(行号),以此获得新的日期,如果新日期都相同,说明是连续的登录天数,如果不同则表示登录天数不连续 .将需要的数据返回

7) 对以上数据,根据其key进行分组 ,value进行聚合处理   reduceByKey  Ordering[String].min/max -取最小值到最大值进行聚合

8) 对以上得到的数据进行再组装 ,然后只留下需要的字段,并将不符合需求的数据过滤掉  map  filter

import java.text.SimpleDateFormat
import java.util.Calendar
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import scala.collection.mutable

object ContinueLogin021 {
  def main(args: Array[String]): Unit = {
    --1 在main方法中传入参数,决定是否在本地运行
    val isLocal = args(0).toBoolean
    --2 获得 conf对象
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
    --3 判断是否在本地运行
    if(isLocal){
      conf.setMaster("local[*]")
    }
    --4 创建 sparkcontext 获取对象
    val sc = new SparkContext(conf)
    --5 通过sc 创建原始RDD ,获得文件数据
    val lines: RDD[String] = sc.textFile(args(1))
    --6 对数据进行切割,得到想要的字段,因为后续调用的方法repartitionAndSortWithinPartitions
    -- repartitionAndSortWithinPartitions:重新分区 ,并且在区里面进行排序
    --该方法后面的参数是必须key ,所有需要将(date,uid)组装成key的位置,并且在区内按 date 进行排序
    val uiddateAndNull: RDD[((String, String), Null)] = lines.map(line => {
      val fields = line.split(",")
      val uid = fields(0)
      val date = fields(1)
      ((date,uid), null)
    }).distinct()

    --7 取出uid ,并对其进行去重(因为同一个用户可以在不同日期进行登录),而我需要得到uid 的个数
    -- 自定义分区器在定义分区规则的时候需要指定需要多少个区(根据uid的个数决定)
    val uids: Array[String] = uiddateAndNull.map(_._1._2).distinct().collect()
    --8 使用自定义的分区器规则 ,将uid放进去 (按照uid进行分区,一个uid一个分区)
    val uidPartitioner = new UidPartitioner1(uids)
    --9 组装的uiddateAndNull: RDD[((String, String), Null)] ,
    -- 调用repartitionAndSortWithinPartitions方法 ,按照自定义分区器规则进行分区 ,并且在区里面进行排序
    val sortedInPartition: RDD[((String, String), Null)] = uiddateAndNull.repartitionAndSortWithinPartitions(uidPartitioner)
    --10 对分区并且拍好序的数据进行处理 ,先一个区一个区的取数据进行处理 (窄依赖mapPartition嵌套map)
    --22 将区域内的数据处理完后,得到返回值((uid, difDate), (date, null, 1))
    --22 需要按照key进行分组,value用来装连续登陆的起始时间,结束时间,和起始到结束的总天数
    val uiddifDateAndDate: RDD[((String, String), (String, String, Int))] = sortedInPartition.mapPartitions(it => { //it =一个区的((date,uid),null)
      --11 获取日历类 ,it数据在map的时候可以一直使用这个,而不是遍历一个it就需要new一个calendar
      val calendar = Calendar.getInstance()
      --12 创建日期格式实例,同上
      val sdf = new SimpleDateFormat("yyyy-MM-dd")
      --13 自定义一个初始化变量 : 行号 (一个it 为一行 ,需要对 it标记号行号,方便后续日期是否连续的计算)
      var rowNumber = 0
      it.map(tp => { //tp = 一个区的每一条 ((date,uid),null)
        --14 每循环一次 ,rowNumber就增加1
        rowNumber += 1
        --15 需要将每条 tp 中的date 和uid取出来 ,方便做处理
        val date = tp._1._1
        val uid = tp._1._2
        --16 将日期字符串转为定义的日期格式
        val date1 = sdf.parse(date)
        --17 将日期格式的数据放到日历类中,方便后对年月日进行加减处理
        calendar.setTime(date1)
        --18 调用add 方法将天取出来 ,减去行号,得到一个新的日期 ,如果得到一连串一样的日期,说明这几天是连续的
        --连续日期增加的 ,每一个日期对应的行号都是随着日期的增加而增加的
        calendar.add(Calendar.DATE, -rowNumber)
        --19 将做好减法的数据取出来
        val time = calendar.getTime
        --20 得到做好减法的新日期
        val difDate = sdf.format(time)
        ((uid, difDate), (date, null, 1)) //21 后续需要对(uid ,difDate)相同的进行分组
      })
    })

    --23 需要对uiddifDateAndDate:((uid, difDate), (date, null, 1)) 数据进行分组聚合,根据key分组,value聚合 ,t1/t2都代表value
    val reduced: RDD[((String, String), (String, String, Int))] = uiddifDateAndDate.reduceByKey((t1, t2) => {
      --24 自定义聚合规则 ,得到两两比较的最小值和最大值 ,然后根据 3号元素进行两两累加
      --获取最大值和最小值时的数值类型为 "整数类型(Int/Long)" ,用 Math 关键字: Math.min/Math.max
      --获取最大值和最小值时的数值类型为 "字符串类型(String)" ,用 Ordering 关键字: Ordering[String].min/Ordering[String].max
      (Ordering[String].min(t1._1, t2._1), Ordering[String].max(t1._1, t2._1), t1._3 + t2._3)
    })
    --25 得到聚合的元素 :((uid,difDate),(startTime,endTime,count))
    --对聚合的元素进行遍历再组装 ,得到想要的结果形式,然后对该结果进行过滤处理 ,筛选过滤掉连续登陆天数小于3天的数据
    val result: RDD[(String, String, String, Int)] = reduced.map(t => (t._1._1, t._2._1, t._2._2, t._2._3)).filter(_._4 >= 3)
    --将结果收集起来 ,然后打印
    println(result.collect().toBuffer)
    sc.stop()
  }

}

--因为不想使用groupBy和sortBy 进行分区排序,因为会产生大量shuffle ,所以就自定义了分区器,重写分区规则
--7  一个用户一个分区器 ,需要自定义一个分区器 (前提是用户数据不多),编写分区规则
class UidPartitioner1(val uids:Array[String]) extends Partitioner{
  --7.1 创建一个HashMap ,用来放uid和其唯一的index值
  val idToIndex = new mutable.HashMap[String, Int]()
  --7.2 定义一个变量 ,初始化为0
  var index = 0
  --7.3 循环遍历,取出第一个uid,将uid和0 放到map集合里面,然后index+1再循环,再生成新的map集合元素
  --也就是每一个uid都对应的是唯一的index ,分区器可以按照这个唯一的index将对应的uid单独分在一个区
  for(uid <- uids){
    idToIndex(uid) =index
    index += 1
  }

  override def numPartitions: Int = uids.length

  --根据key的规则进行分区 uiddateAndNull: RDD[((String, String), Null)]=((date,uid),null)
  override def getPartition(key: Any): Int = {
    --根据key,将元祖里面的uid取出来
    val uid = key.asInstanceOf[(String, String)]._2
    --然后根据uid ,得到对应的分区号
    idToIndex(uid)
  }
}

3  main方法中参数的导入

因为在程序中要求要从main方法参数中得到数据(args(index)),如果不导入代码运行时需要的参数数据 ,代码在运行的时候就会报异常 :Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0.

在main方法中导入参数方法如下图:

 

案例二 : 计算出用户的下行流量(要求:但该次开始时间-上次结束时间 > 10min 时,将流量分开统计为两组,如果在10min之内 ,则将流量都相加起来为一组)

数据如下 :

原始数据
1,2020-02-18 14:20:30,2020-02-18 14:46:30,20
1,2020-02-18 14:47:20,2020-02-18 15:20:30,30
1,2020-02-18 15:37:23,2020-02-18 16:05:26,40
1,2020-02-18 16:06:27,2020-02-18 17:20:49,50
1,2020-02-18 17:21:50,2020-02-18 18:03:27,60
2,2020-02-18 14:18:24,2020-02-18 15:01:40,20
2,2020-02-18 15:20:49,2020-02-18 15:30:24,30
2,2020-02-18 16:01:23,2020-02-18 16:40:32,40
2,2020-02-18 16:44:56,2020-02-18 17:40:52,50
3,2020-02-18 14:39:58,2020-02-18 15:35:53,20
3,2020-02-18 15:36:39,2020-02-18 15:24:54,30


在实现需求时 ,需要将以上数据处理成以下数据才好实现需求
--startTime - 上一次endTime >10min 就 mark标记为1,否则标记为0
--flag=本次mark+上次mark ,相同时为一组,都是0时为1组 ,都是1是为1组 ,为2时有时另一组
uid  ,startTime      , endTime       ,上一次接收时间 upendTime   mark             flag
1,2020-02-18 14:20:30,2020-02-18 14:46:30,20,2020-02-18 14:20:30,0                0
1,2020-02-18 14:47:20,2020-02-18 15:20:30,30,2020-02-18 14:46:30,0                0

1,2020-02-18 15:37:23,2020-02-18 16:05:26,40,2020-02-18 15:20:30,1                1
1,2020-02-18 16:06:27,2020-02-18 17:20:49,50,2020-02-18 16:05:26,0                1
1,2020-02-18 17:21:50,2020-02-18 18:03:27,60,2020-02-18 17:20:49,0                1

2,2020-02-18 14:18:24,2020-02-18 15:01:40,20
2,2020-02-18 15:20:49,2020-02-18 15:30:24,30
2,2020-02-18 16:01:23,2020-02-18 16:40:32,40
2,2020-02-18 16:44:56,2020-02-18 17:40:52,50
3,2020-02-18 14:39:58,2020-02-18 15:35:53,20
3,2020-02-18 15:36:39,2020-02-18 15:24:54,30

 需求实现方法如下 : 

大概思路,具体步骤以代码块中描述的为准 :

1) 创建 SparkContext ,获得sc, 然后通过sc 创建原始的RDD ,获得文件数据,对数据进行切割处理,对日期进行格式化 ,组装成元组mapPartition,SimpleDateFormat,map,split

2) 按key进行分组,将同一组数据的value展开 (groupByKey  flatMapValues-iter )  对同一组但value不一样的数据按value中的Time 进行排序(sortBy) ,然后排序后的数据进行遍历(map) ,遍历时,对迭代器的每一条数据进行处理, 本次登录时间 - 上次登录结束时间如果大于等于10min ,就标记(flag)为1 ,小于10min就标记为0 .然后对本次标记的数据跟上次标记的数据进行相加处理,得到一个新的标记(sum) ,sum相同的数据表示为同一用户相同组,其流量可以累积起来,如果sum不同则表示为同一用户不同组,流量需要分开统计. 将需要的数据组装起来,(uid,sum)放在key的位置 value是开始时间和结束时间和流量

3) 根据key 进行分组 ,value进行聚合  reduceByKey   mapPartition   Math.min/max

4) mapPartition SimpleDateFormat map--->将以上得到的数据的时间戳格式化成年月日时分秒 ,然后只留下需求的字段.

import java.text.SimpleDateFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object FlowCount03 {
  def main(args: Array[String]): Unit = {
    --1 根据在main方法中传入的布尔值,来判断程序是否在本地运行,这样使得程序变得更灵活
    val isLocal = args(0).toBoolean
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
    --2 判断main方法中传出的参数是否为true,程序是否需要在本地运行
    if(isLocal){
      conf.setMaster("local[*]")
    }
    --3 创建sparkContext
    val sc = new SparkContext(conf)
    --4 通过sc ,创建原始的RDD ,获得数据
    val lines: RDD[String] = sc.textFile(args(1))
    --5 当执行的都是窄依赖(不产生shuffle)的Tfansformation或者是同名的Transformation时 ,我们可以将他们合并到同一个函数里面
    --mapPartition :以分区为单位进行对应操作,一个分区就是一个迭代器,该函数必须返回迭代器
    val uidAndTimeFlow: RDD[(String, (Long, Long, Double))] = lines.mapPartitions(it => { --it:一个迭代器
      --5.1 定义一个日期格式化类
      val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
      --5.2 map :处理一条一条的数据 ,数据形式为 : 1,2020-02-18 14:20:30,2020-02-18 14:46:30,20
      it.map(line => {
        --5.3 对一条一条的数据进行切割,一行数据返回一个数组
        val fields: Array[String] = line.split(",")
        --5.4 取数据对应的索引号index的元素处理
        val uid = fields(0)
        --5.5 将取出来的年月日时分秒数据转化为时间戳
        val startTime = fields(1)
        val startTimestamp = sdf.parse(startTime).getTime
        val endTime = fields(2)
        val endTimestamp = sdf.parse(endTime).getTime
        val downFlow = fields(3).toDouble
        --5.6 将取出来的元素组装,uid为key ,其他元素都为value
        (uid, (startTimestamp, endTimestamp, downFlow))
      })
    })
    --6 以上的数据为:(uid,(startTimestamp,endTimestamp,downFlow)),
    --6 然后根据uid进行分组,然后将同一个uid的value数据展平 ,根据value里面的Time进行排序
    val uidAndTimeFlowSum: RDD[(String, (Long, Long, Double, Int))] = uidAndTimeFlow.groupByKey().flatMapValues(it => {
      --6.1 it : (startTimestamp, endTimestamp, downFlow)---先处理同一个uid的不同value
      val sorted: List[(Long, Long, Double)] = it.toList.sortBy(_._1)
      --6.2 对排好序的(startTimestamp, endTimestamp, downFlow)进行遍历---先处理同一个uid的不同value
      var temp = 0L    --用来给 endTimestamp 赋值的变量
      var flag = 0     --当本次登录时间 - 上次登录结束时间>10min 时,标记为1,否则标记为0
      var sum = 0      --对标记为1或者为0的这次和上次标记进行累加 ,最后数值相同的为同一个uid的不同组数据
      sorted.map(tp => {
        --6.3 将value里面的元素先取出来备用
        val startTimestamp = tp._1
        val endTimestamp = tp._2
        val flow = tp._3
        --6.4 如果temp 已经被赋予上一次登陆结束的时间
        if (temp != 0L) {
          --6.5 那就用本次开始的时间 - temp ,如果时间间隔大于 10min ,那就标记为1
          if ((startTimestamp - temp) / 60000 > 10) {
            flag = 1
          } else {
            --6.6 时间间隔在10min之内 ,标记为0
            flag = 0
          }
        }
        --6.7 sum=flag  ,sum =上一次计算的sumg + 本次标记的flag
        --6.7 如果sum相同,说明是一组的数据 ,超过10min被标记为1之后,sum值就变了,就要算同一用户另外一组流量数据了
        sum += flag
        --6.8 如果temp没有被赋值 ,那就将本次的登录结束时间赋值给temp
        temp = endTimestamp
        (startTimestamp, endTimestamp, flow, sum)
      })
    })
    --7 以上得到的数据是 :(uid,(startTimestamp, endTimestamp, flow, sum)),需要对这个数据进行重新组装
    --7 组装成 :((uid, sum), (start, end, flow)),然后对这个元组的key进行分组 ,其value进行聚合
    val reduced: RDD[((String, Int), (Long, Long, Double))] = uidAndTimeFlowSum.map {
      case (uid, (start, end, flow, sum)) => ((uid, sum), (start, end, flow))
    }.reduceByKey((t1, t2) => {
      --7.1 (uid,sum)相同时 ,获取两个value值中的 startTime的最小值和endTime 的最大值,然后对flow值进行累加
      --获取最大值和最小值时的数值类型为 "整数类型" ,用 Math 关键字: Math.min/Math.max
      --获取最大值和最小值时的数值类型为 "字符串类型" ,用 Ordering 关键字: Ordering[String].min/Ordering[String].max
      (Math.min(t1._1, t2._1), Math.max(t1._2, t2._2), t1._3 + t2._3)
    })
    --7.2 对聚合的结果 ,将时间戳数据格式化回日期的年月日时分秒格式 ,然后只留下符合需求的数据即可
    val result: RDD[(String, String, String, Double)] = reduced.mapPartitions(it => {
      --7.3 定义格式化日期类 ,用来将时间戳格式化回原来的 年月日时分秒 的形式
      val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
      it.map {
            --7.4 直接用case匹配要遍历的数据 ,方便后续取值
        case ((uid, sum), (start, end, flow)) => {
          --7.5 因为之前的字段已经经过匹配,现在直接去匹配里面的字段名字就可以取出对应的值
          --7.5 将取出来的值(时间戳)格式化 年月日时分秒 类型
          val startTime = sdf.format(start)
          val endTime = sdf.format(end)
          --7.6 然后将数据重新组装,只留下需求的字段
          (uid, startTime, endTime, flow)
        }
      }
    })
    --8 因为处理的结果可能在不同的区或不同的组里面 ,这里打印时需要将数据收集起来
    println(result.collect().toBuffer)
    /**
     * 注 : 收集起来的结果是混乱的没有排序的,并不会根据uid进行分组排序
     * 结果为 : ArrayBuffer((2,2020-02-18 15:20:49,2020-02-18 15:30:24,30.0)
     * (2,2020-02-18 16:01:23,2020-02-18 17:40:52,90.0) 
     * (2,2020-02-18 14:18:24,2020-02-18 15:01:40,20.0)
     * (1,2020-02-18 14:20:30,2020-02-18 15:20:30,50.0)
     * (1,2020-02-18 15:37:23,2020-02-18 18:03:27,150.0)
     * (3,2020-02-18 14:39:58,2020-02-18 15:35:53,50.0)
     */
     sc.stop()
  }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值