spark03--textFile分区算法,常用算子使用,启动,任务提交流程, 基站案例

本文深入探讨了Spark的分区算法、常用算子应用、集群及任务提交流程,详细讲解了基站案例的解决思路与过程,是Spark开发者提升技能的实用指南。

一 textFile分区算法

小文件 --> (指定分区数量/文件数量)[向上取整] * 文件数量

源码粗略解析

  1. 求出文件总totalSize, 获取指定分区数量num
  2. 获取文件大小size和块大小blockSize的最小值, 这里假设文件大小小于块大小
  3. bytesRemaining = [totalSize/num] 使用文件总长度除以分区数量取整
  4. bytesRemaining/splitSize > 1.1 遍历所有文件, 使用文件总长度除以指定分区数量, 如果bytesRemaining除以文件数量大于1.1的冗余, 则分成两个分区, 否则生成一个分区

实例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. (1078+2081)/2=1579.5 取整 1579
  2. 遍历第一个文件: 1078/1579<1.1, 直接分成一个区
  3. 遍历第二个文件: 2081/1579>1.1, 生成一个分区后还剩2081-1579=0.31<1.1, 生成另一个分区
  4. 总共的分区加在一起是三个分区

二 常用算子使用

2.1 map mapPartitions

和map类似, 遍历每一个分区, 对每一个分区进行操作, 有返回值

mapPartitions   f: Iterator[T] => Iterator[U]

在这里插入图片描述


2.2 mapPartitionsWithIndex

可以使用分区下标进行操作分区

参数列表:(f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false)

在这里插入图片描述


2.3 aggregate

聚合算子, 是一个柯里化, 可以传入两个参数列表, 第一个参数列表可以传入一个默认值, 第二个参数列表传入两个函数, 在聚合时, 先将传入的默认值加入到每一个分区中, 然后每一个分区根据传入的第一个函数进行聚合, 当每个分区聚合完成之后, 在根据传入的第二个函数对分区进行聚合操作, 在这一次聚合时, 默认值也算进来.
需要注意的是, 在第二次聚合时, 由于是多个线程并行执行, 所以当字符串在第二次聚合时不分先后, 结果也是不确定地.

(zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


2.4 aggregateByKey

和aggregate类似, 只不过aggregateByKey操作的是键值对, 对key相同的进行聚合

(zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U, combOp: (U, U) => U): RDD[(K, U)

在这里插入图片描述


2.5 combineByKey

combineByKey: 需要三个参数, 第一个参数是创建一个Combiner的函数, 第二个参数是局部聚合函数, 第三个参数是全局聚合函数

(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)

在这里插入图片描述

在这里插入图片描述

这里是创建两个List集合, 然后进行拉链操作, 将两个集合合并一下. 这里做的是将个数为1的动物放在一起, 将个数为2的动物放在一起
在这里插入图片描述


2.6 countByKey和countByValue

countByKey: 对键值对进行操作, 对相同的key进行累加操作
countByValue: 对完全相同的元素进行累加求和[容易忽略]
在这里插入图片描述


2.7 filterByRange

根据输入的key的范围进行过滤, 例如输入a,d 那么会根据key的字典顺序进行筛选, 选出属于a到d范围内的元素
在这里插入图片描述


2.8 flatMapValues

和flatMap类似, 这里操作的是values, 对values进行扁平化处理以及map操作, 操作完成之后将value 和对应的key组成一个元组
在这里插入图片描述


2.9 foldByKey

将key相同的value通过传入的运算进行操作
在这里插入图片描述

2.10 foldByKey实现Wordcount

在这里插入图片描述


2.11 foreachPartition

和foreach类似, foreachPartition是遍历每一个分区, 对每一个分区进行操作, 需要注意的是foreachPartition没有返回值, 所以在shell中看不到效果
在这里插入图片描述

在这里插入图片描述


2.12 keyBy

根据传入的函数进行操作, 将返回值作为key, 和原来的值拼成一个元组
在这里插入图片描述


2.13 keys values

可以获取到一个元组的key或者values
在这里插入图片描述


2.14 collectAsMap

collect的返回值是一个Array, 而在调用collectAsMap后, 返回值是一个Map
在这里插入图片描述


2.15 repartition, coalesce, partitionBy
  • 在spark中重新分区的算子只有这三个
  1. repartition: 一定会发生shuffle过程, 本身不会发生重新分区, 而是会返回一个新的分区

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

  2. coalesce: 默认shuffle=false(不发生shuffle), 分区变多时需要手动指定shuffle为true, 否则分区数量不会改变, 而分区变少时, 不需要刻意指定shuffle, 因为分区变少不需要发生shuffle过程
    在这里插入图片描述
    在这里插入图片描述

  3. partitionBy: 参数是一个分区器, 需要自己传入一个分区器. 需要注意的是只有 k–>v形式的值 才能调用partitionBy的方法.
    当某一个分区数据量比较大的时候会采取将这个分区拆分成多个分区
    在这里插入图片描述
    在这里插入图片描述


2.16 checkpoint

checkpoint可以将重要的中间结果集写入到磁盘, 方便下次直接使用, 无需再次运行, 提高效率. 可以将中间结果集放到本地磁盘或者HDFS上, 推荐放到HDFS上, HDFS多个副本, 保证数据的安全性

步骤:

  1. 指定一个目录
  2. 调用checkPoint算子(延迟加载)
    在这里插入图片描述

三 spark集群启动流程

spark不同版本的RPC通信框架

spark 1.3--------->actor
spark 1.3~1.5--->AKKA
spark 1.6--------->AKKA+netty
spark 2.0--------->netty

spark集群启动流程

  1. Driver发送start-all.sh启动Master服务
  2. Master启动之后会启动一个定时器, 定时检查超时的Worker
  3. 通过解析slaves配置文件, 开始在响应的节点启动Worker服务
  4. Worker启动之后, 开始向Master发送注册信息
  5. Master收到注册信息后, 保存并响应客户端注册成功信息, 即发送给Worker一个URL
  6. Worker收到URL, 保存URL并定时向Master发送心跳信息

四 spark任务提交流程

任务提交命令

spark-submit \
--master spark//hadoop:7077 \
--executor-memory 1024g \
--total-executor-cores 2 \
.......jar args

任务提交流程

  1. Driver调用 spark-submit 提交任务, 向Master进行注册(发送任务描述信息)
  2. Master收到任务描述, 开始生成任务信息, 并将任务描述信息放到队列中
  3. Master通知Worker启动子进程Executor, 并发送任务信息
  4. Worker接收任务信息, 启动Executor
  5. Executor向Driver端进行注册
  6. Driver将生成的Task发送给相应的Executor
  7. Executor开始执行具体的任务
    在这里插入图片描述

五 基站案例

5.1需求

根据用户进出基站的信息以及基站的基本信息求出用户所在时间最长的两个基站id

5.2已有的两个文件

文件1 用户进出基站信息
手机号, 时间戳, 基站id, 时间类型(1:进站, 0:出站)
在这里插入图片描述

文件2 基站基本信息
基站id, 经纬度,…
在这里插入图片描述

5.3 求解思路

根据第一个文件求出用户在每一个基站的停留时间. 然后和第二个文件进行join操作, 然后进行排序, 取出时间戳最大的前两个元素

5.4 求解过程
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object MobileLocation {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("MobileLocation").setMaster("local")
    val sc = new SparkContext(conf)

    //获取用户访问信息
    val files: RDD[String] = sc.textFile("G://06-Spark/sparkcoursesinfo/spark/data/lacduration/log")
    //切分用户访问信息
    val splitedUserInfo: RDD[((String, String), Long)] = files.map(line => {
      val fields: Array[String] = line.split(",")
      val phone: String = fields(0) //手机号
      val time: Long = fields(1).toLong //时间戳
      val lac: String = fields(2) //基站id
      val eventType: Int = fields(3).toInt
      val time_long = if (eventType == 1) -time else time //如果事件类型为1 , 将时间戳变成负的
      ((phone, lac), time_long)
    })

    // 计算用户在基站停留的总时长
    val aggred: RDD[((String, String), Long)] = splitedUserInfo.reduceByKey(_ + _)

    // 为了方便和基站进行join, 将数据进行调整
    val lacAndPhoneAndTime: RDD[(String, (String, Long))] = aggred.map(tup => {
      val phone: String = tup._1._1 // 手机号
      val lac: String = tup._1._2 //基站id
      val time: Long = tup._2 // 用户在某个基站停留的总时长
      (lac, (phone, time))
    })


    // 获取基站信息
    val lacInfo: RDD[String] = sc.textFile("G://06-Spark/sparkcoursesinfo/spark/data/lacduration/lac_info.txt")
    // 切分基站信息
    val splitedLacInfo: RDD[(String, (String, String))] = lacInfo.map(line => {
      val fields: Array[String] = line.split(",")
      val lac: String = fields(0) // 基站id
      val x: String = fields(1) // 经度
      val y: String = fields(2) // 纬度
      (lac, (x, y))
    })

    // 用户信息和基站信息进行join
    val joined: RDD[(String, ((String, Long), (String, String)))] = lacAndPhoneAndTime.join(splitedLacInfo)

    val lacAndPhoenAndTimeAndXY: RDD[(String, String, Long, (String, String))] = joined.map(tup => {
      val lac: String = tup._1 //基站id
      val phone: String = tup._2._1._1 // 手机号
      val time: Long = tup._2._1._2 // 停留时长
      val xy: (String, String) = tup._2._2 // 经纬度
      (lac, phone, time, xy)
    })

    // 为了便于统计用户在所有基站的信息 生成用户粒度的数据
    val grouped: RDD[(String, Iterable[(String, String, Long, (String, String))])] = lacAndPhoenAndTimeAndXY.groupBy(_._2)

    // 按照时长进行组内排序
    //grouped.map(x=>(x._1,x._2.toList.sortBy(_._3).reverse))
    val sorted: RDD[(String, List[(String, String, Long, (String, String))])] = grouped.mapValues(_.toList.sortBy(_._3).reverse)

    // 获取top2
    val res: RDD[(String, List[(String, String, Long, (String, String))])] = sorted.mapValues(_.take(2))

    println(res.collect.toBuffer)

    sc.stop()
  }

}

5.5 运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值