案例一 : 计算出连续登陆三天的用户
重要的算子 : 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()
}
}