数据
用户id,开始时间,结束时间,所用流量
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
需求
求每个用户每个阶段用的总流量,并显示该阶段的开始时间和结束时间,阶段划分规则为:该次登录时间减去上去结束时间超过10分钟,如果超过10分钟就判定为下一个阶段,没超过就所属当前阶段
SQL思想
写sql时将先将结束时间往下压一行,然后就可以使用当前行的开始时间减结束时间,判断是否大于10分钟,大于就给1,小于就给个0,然后调用sum over()将当前行的0或1往上一路叠加,然后就能将每个阶段分开,从而分组聚合
RDD代码实现
package com.doit.spark.day01
import java.text.SimpleDateFormat
import java.util.logging.SimpleFormatter
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import scala.collection.mutable
object FlowStatistics {
def main(args: Array[String]): Unit = {
//传入是否在本地运行
val setMaster = args(0).toBoolean
//传入求的每个学科的受欢迎的老师的数量
val conf: SparkConf = new SparkConf().setAppName("FlowStatistics")
//判断是否在本地运行
if (setMaster){
conf.setMaster("local[*]")
}
val sc = new SparkContext(conf)
//传入文件路径
val filePath = args(1)
val line: RDD[String] = sc.textFile(filePath)
val userFlow: RDD[(String, String, String, String)] = line.map(x => {
val arr: Array[String] = x.split(",")
val uid: String = arr(0)
val startTime: String = arr(1)
val endTime: String = arr(2)
val flow: String = arr(3)
(uid, startTime, endTime, flow)
})
//对用户分组之后,调用flatMapValues对迭代器value处理后再对它压平
val value: RDD[(String, (String, String, String, Int, Int))] = userFlow.groupBy(_._1).flatMapValues((it: Iterable[(String, String, String, String)]) => {
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
//定义一个阶段标识,初始值为0
var index = 0
//定义一个时间的中间值,起始值为null
var timeTemp: String = null
//立一个flag,判断是否为该组内的第一条数据
var flag: Boolean = true
//用map变量组内数据
it.map(flow => {
//判断是否为第一条数据
if (flag) {
//是就将flag = false
flag = false
//并当前的结束时间赋给中间值
timeTemp = flow._3
} else {
//不是第一条数据,就该条数据的登录时间和上条数据的结束时间(中间值)是否超过十分钟
val startTime: Long = sdf.parse(flow._2).getTime
val endTime: Long = sdf.parse(timeTemp).getTime
val l: Long = (startTime -endTime) / 1000 / 60
if (l > 10) {
//是,就给阶段标识加1
index += 1
}
//处理完之后,也要将当前的结束时间赋给中间值
timeTemp = flow._3
}
//返回:用户,开始时间,结束时间,流量,分组标识
(flow._1, flow._2, flow._3, flow._4.toInt, index)
})
})
//调用reduceByKey获取当前阶段内起始时间的和结束时间并加总阶段流量
//因为reduceByKey要求输入类型和输出类型相同,所以需要用map转换,并注意将用户和阶段标识统一作为key
val maped: RDD[((String, Int), (String, String, Int))] = value.map(x => ((x._1, x._2._5), (x._2._2, x._2._3, x._2._4)))
val reduceByKeyed: RDD[((String, Int), (String, String, Int))] = maped.reduceByKey((v1, v2) => {
(Ordering[String].min(v1._1, v2._1), Ordering[String].max(v1._2, v2._2), v1._3 + v2._3)
}).sortByKey()
println(reduceByKeyed.collect().toBuffer)
}
}