摘要
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
}