Spark_小练习_大数据量相邻数据合并

Spark面试题

个人练习,思路可能并不正确,数据合并中逻辑不完善,谨慎观看

现有如下数据文件需要处理
格式:CSV
位置:hdfs://myhdfs/input.csv
大小:100GB
字段:用户ID,位置ID,开始时间,停留时长(分钟)
4行样例:
UserA,LocationA,2018-01-01 08:00:00,60
UserA,LocationA,2018-01-01 09:00:00,60
UserA,LocationB,2018-01-01 10:00:00,60
UserA,LocationA,2018-01-01 11:00:00,60
解读:样例数据中的数据含义是:
用户UserA,在LocationA位置,从8点开始,停留了60分钟
用户UserA,在LocationA位置,从9点开始,停留了60分钟
用户UserA,在LocationB位置,从10点开始,停留了60分钟
用户UserA,在LocationA位置,从11点开始,停留了60分钟
该样例期待输出:

UserA,LocationA,2018-01-01 08:00:00,120

UserA,LocationB,2018-01-01 10:00:00,60

UserA,LocationA,2018-01-01 11:00:00,60
处理逻辑:

1、对同一个用户,在同一个位置,连续的多条记录进行合并

2、合并原则:开始时间取最早时间,停留时长加和
要求:请使用Spark、MapReduce或其他分布式计算引擎处理

思路

  1. 首先使用范围分区器与repartitionAndSortWithinPartitions算子将数据分成100份,每份1G左右同时根据时间排序。
  2. 加盐并完成第一次两两合并。此时我们可以保证每个mapTask处理的数据是有序且合并完毕的但是我们不能保证mapA的最后一条与mapB的第一条不是相关联的
  3. 重新设置分区,使得repartitionAndSortWithinPartitions边界发生变化,之后对数据重新排序合并。此时数据应该是符合要求的数据

函数测试时使用分区数为4与1
测试数据

UserA,LocationB,2018-01-01 07:00:00,60
UserA,LocationA,2018-01-01 08:00:00,60
UserA,LocationA,2018-01-01 09:00:00,60
UserA,LocationB,2018-01-01 10:00:00,60
UserA,LocationA,2018-01-01 11:00:00,60
UserA,LocationA,2018-01-01 12:00:00,60
UserA,LocationA,2018-01-01 13:00:00,60
UserA,LocationA,2018-01-01 14:00:00,60

结果如下:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

代码如下:

import java.text.SimpleDateFormat
import java.util.Calendar

import org.apache.spark.rdd.RDD
import org.apache.spark.{RangePartitioner, SparkConf, SparkContext, TaskContext}

import scala.collection.mutable.ArrayBuffer

object Interview {


  /**
   * 思路:
   * 首先使用范围分区器与repartitionAndSortWithinPartitions算子将数据分成100份,每份1G左右同时根据时间排序。
   * 之后加盐并完成第一次两两合并。
   * 此时我们可以保证每个mapTask处理的数据是有序且合并完毕的但是我们不能保证mapA的最后一条与mapB的第一条不是相关联的
   * 故此重新设置分区,使得repartitionAndSortWithinPartitions边界发生变化,之后对数据重新排序合并。
   * 此时数据应该是符合要求的数据
   *
   * 函数测试时使用分区数为4与1
   */
  def main(args: Array[String]): Unit = {
    
    // 获取核心对象
    var sc = new SparkContext(new SparkConf().setAppName("SparkTest")
      .setMaster("local[*]").set("spark.testing.memory", "2147480000"))
      
	var path = "hdfs://myhdfs/input.csv" // 文件路径
    var file = sc.textFile(path) // 获取输入rdd

    mainLogic(file, sc) // 主要逻辑处理
  }

  /**
   * 指定比较规则->数据拆分转换为rdd->分区且排序->合并->重新分区排序->合并
   *
   * @param file 输入文件
   * @param sc
   */
  def mainLogic(file: RDD[String], sc: SparkContext) {
    // 指定repartitionAndSortWithinPartitions排序规则
    implicit val my_self_Ordering: Ordering[String] = new Ordering[String] {
      override def compare(a: String, b: String): Int = {
        TimeComparison(a, b) // 时间排序
      }
    }
    
    // 数据切片
    var splitfile: RDD[(String, (String, String, String))] = file.map(input => {
      var array = input.split(",") // 数据拆分
      // 时间为key,其余为value
      (array(2), (array(0), array(1), array(3)))
    })

    // 第一次分区完毕的数据,此时数据分区有序,一个reduce差不多处理1G的数据
    var firstPartitionFile: RDD[(String, (String, String, String))] = splitfile.repartitionAndSortWithinPartitions(new RangePartitioner(4, splitfile))
    firstPartitionFile.foreach(println)
    println("⬆⬆⬆⬆⬆⬆⬆⬆⬆第一次分区排序⬆⬆⬆⬆⬆⬆⬆⬆⬆")

    // 第一次合并
    var firstArrangefile: RDD[(String, (String, String, String))] = mapProcess(firstPartitionFile)
    firstArrangefile.foreach(println)
    println("⬆⬆⬆⬆⬆⬆⬆⬆⬆第一次合并结果⬆⬆⬆⬆⬆⬆⬆⬆⬆")

    // 第二次分区
    var secondPartitionFile: RDD[(String, (String, String, String))] = firstArrangefile.repartitionAndSortWithinPartitions(new RangePartitioner(1, firstArrangefile))
    secondPartitionFile.foreach(println)
    println("⬆⬆⬆⬆⬆⬆⬆⬆⬆第二次分区排序⬆⬆⬆⬆⬆⬆⬆⬆⬆")

    // 第二次合并
    var secondArrangeFile: RDD[(String, (String, String, String))] = mapProcess(secondPartitionFile)
    secondArrangeFile.foreach(println)
    println("⬆⬆⬆⬆⬆⬆⬆⬆⬆第二次合并结果⬆⬆⬆⬆⬆⬆⬆⬆⬆")
  }

  /**
   * 概述:传入有序rdd,进行相邻数据的单个map合并,
   * 详情:加盐保证不会数据倾斜,使用groupbu获得迭代器,使用map并行合并,合并结果作为ArrayBuffer数组进行存储,使用map与flatMap算子将数组转换为rdd算子
   *
   * @param partitionFile
   * @return
   */
  def mapProcess(partitionFile: RDD[(String, (String, String, String))]): RDD[(String, (String, String, String))] = {
    // 加盐,groupbykey获得迭代器
    var groupFile: RDD[(Int, Iterable[(String, (String, String, String))])] = partitionFile.map((TaskContext.get.partitionId, _)).groupByKey()
    // 局部变量,存储两两合并的临时结果
    var transit: (String, (String, String, String)) = (":", (":", ":", ":")) // 存储当前数据
    //(2018-01-01 10:00:00,(UserA,LocationB,60))
    // 数组合并
    var firstFile = groupFile.map {
      case (partitionID, values) => {
        var arrayBuffer: ArrayBuffer[(String, (String, String, String))] = ArrayBuffer[(String, (String, String, String))]()
        // 为空说明是第一次,获取第一行数据
        for (value <- values) {
          if (transit._1 == ":") {
            transit = (value._1, (value._2._1, value._2._2, value._2._3))
          }
          // 时间地点连续
          else if (transit._2._1 == value._2._1 && transit._2._2 == value._2._2 && TimeContinuous(transit._1, value._1, transit._2._3)) {
            transit = (transit._1, (transit._2._1, transit._2._2, (Integer.parseInt(transit._2._3) + Integer.parseInt(value._2._3)).toString)) // 重新设置时间
          } else {
            // 不是第一次、也不是连续的地点那么说明记录断开则记录上一次的地点,同时更新当前记录
            var test = transit
            transit = (value._1, (value._2._1, value._2._2, value._2._3))
            arrayBuffer += test
          }
        }
        arrayBuffer += transit
        arrayBuffer
      }
    }
    // 数组转换为算子
    firstFile.map(values => {
      var buffer = new StringBuffer
      for (value <- values) {
        buffer.append(value._1 + "," + value._2._1 + "," + value._2._2 + "," + value._2._3 + "#")
      }
      buffer.substring(0, buffer.length() - 1)
    }).flatMap(_.split("#")).map(input => {
      var array = input.split(",") // 数据拆分
      // 时间为key,其余为value
      (array(0), (array(1), array(2), array(3)))
    })
  }

  /**
   * 计算增量时间,判断是否相等
   *
   * @param time1         时间1
   * @param time2         时间2
   * @param timeIncrement 时间1的时间增量(停顿时间)
   * @return
   */
  def TimeContinuous(time1: String, time2: String, timeIncrement: String): Boolean = {
    var simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    var date1 = simpleDateFormat.parse(time1) // 数据类型转换
    var date2 = simpleDateFormat.parse(time2) // 数据类型转换
    var cal = Calendar.getInstance(); // 时间计算
    cal.setTime(date1);
    cal.add(Calendar.MINUTE, Integer.parseInt(timeIncrement)); // 24小时制
    date1 = cal.getTime
    // 使用Date的compareTo()方法,大于、等于、小于分别返回1、0、-1
    // 相等为true
    // 不等为false
    if (date1.compareTo(date2) == 0) true
    else false
  }

  /**
   * 时间比较函数,为repartitionAndSortWithinPartitions准备
   *
   * @param time1 时间1
   * @param time2 时间2
   * @return 比较结果
   *         大于 1
   *         等于 0
   *         小于-1
   */
  def TimeComparison(time1: String, time2: String): Int = {
    // 获取核心对象,指定数据类型
    val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    val date1 = simpleDateFormat.parse(time1) // 数据类型转换
    val date2 = simpleDateFormat.parse(time2) // 数据类型转换
    //使用Date的compareTo()方法,大于、等于、小于分别返回1、0、-1
    date1.compareTo(date2)
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值