(spark源码)randomSplit算子

摘要

ods层是高度聚合的数据, 经常需要将一张大表拆分为多张小表.
hive的分表基本只能依靠when, 通过多次遍历完成过滤, 但由于spark可以指定分区, 于是考虑借助自定义分区后, 借助分区直接对表进行拆分
先说结论: 思路失败, 建议选择filter或者when完成分表
本文用于记录思路和简析randomSplit方法

思路

如果api中有相应的方法, 可以直接拆分单表为多表, 那么该方法返回的结果, 应该是一个rdd, dataset 或者dataframe的集合.
于是查找了相关的api, 发现还真有一个:

  def randomSplit(weights: Array[Double]): Array[Dataset[T]] = {
    randomSplit(weights, Utils.random.nextLong)
  }

该方法传入一个全正数的Double数组, 将Dataset按对应百分比进行随机切分.
我们的需求是按照分区进行切分, 说不定可以借鉴下该方法. 于是追了下源码

源码

// seed是随机数的种子
def randomSplit(weights: Array[Double], seed: Long): Array[Dataset[T]] = {
  // 对Double数组进行健壮性判断 
  require(weights.forall(_ >= 0),
    s"Weights must be nonnegative, but got ${weights.mkString("[", ",", "]")}")
  require(weights.sum > 0,
    s"Sum of weights must be positive, but got ${weights.mkString("[", ",", "]")}")

  // 该方法抽取元素的逻辑, 以随机切分含有5个元素的数组为例
  // 使用种子随机出1和3, 那么先取第一个和第三个, 再取剩下的
  // 因此就要求数组必须全局有序, 下述代码就进行了排序
  val sortOrder = logicalPlan.output
    .filter(attr => RowOrdering.isOrderable(attr.dataType))
    .map(SortOrder(_, Ascending))
  val plan = if (sortOrder.nonEmpty) {
    Sort(sortOrder, global = false, logicalPlan)
  } else {
    // 如果无法排序, 就缓存元素到内存中
    cache()
    logicalPlan
  }
  val sum = weights.sum
  // scanLeft和foldLeft有些类似, 举例来说, Array(1, 2, 3), 
  // 给个初始值0, fold计算结果是6, 而scan是Array(0, 1, 3, 6)
  val normalizedCumWeights = weights.map(_ / sum).scanLeft(0.0d)(_ + _)
  // sliding是个滑动的窗口, 默认步长为1, 返回一个iterator
  // Array(0, 1, 3, 6)对应为(0, 1), (2, 3), (3, 6)
  normalizedCumWeights.sliding(2).map { x =>
    // new了多个对象以返回Array
    new Dataset[T](
      // 所以方法的实质是进行了多次Sample抽样
      sparkSession, Sample(x(0), x(1), withReplacement = false, seed, plan)(), encoder)
  }.toArray
}

看到这里, 发现和预想的不同, randomSplit也是靠多次遍历完成的切分.
那么基本上该方法对我们的需求, 是没有帮助的

垃圾时间

看一下Dataset如何被new出来

// 这是构造方法的重写
// sparkSession是连接对象, encoder是编译器(将元素编译为Dataset)
// 比较难以理解的就是logicalPlan(逻辑计划)
def this(sparkSession: SparkSession, logicalPlan: LogicalPlan, encoder: Encoder[T]) = {
  this(sparkSession, sparkSession.sessionState.executePlan(logicalPlan), encoder)
}

// 刚刚的方法涉及到Sample这个逻辑计划
case class Sample(
    lowerBound: Double,
    upperBound: Double,
    withReplacement: Boolean,
    seed: Long,
    child: LogicalPlan)(
    // 父类是UnaryNode, UnaryNode继承自LogicalPlan
    val isTableSample: java.lang.Boolean = false) extends UnaryNode {

  override def output: Seq[Attribute] = child.output

  override def computeStats(conf: SQLConf): Statistics = {
    // 二者之差, 就是此Dataset所占的百分比
    val ratio = upperBound - lowerBound
    // 下述代码描述采样逻辑
    val childStats = child.stats(conf)
    var sizeInBytes = EstimationUtils.ceil(BigDecimal(childStats.sizeInBytes) * ratio)
    if (sizeInBytes == 0) {
      sizeInBytes = 1
    }
    val sampledRowCount = childStats.rowCount.map(c => EstimationUtils.ceil(BigDecimal(c) * ratio))
    // 采样后无法确定源数据的分部
    Statistics(sizeInBytes, sampledRowCount, hints = childStats.hints)
  }

  override protected def otherCopyArgs: Seq[AnyRef] = isTableSample :: Nil
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值