Hive排序函数源码解密:字节跳动面试官的底层三连问
作为数据工程师,理解Hive排序函数的源码就像掌握汽车的发动机原理。本文通过字节跳动内部技术文档,为你揭示三大排序函数的源码级实现差异。
一、分布式执行框架
Hive中ROW_NUMBER
、RANK
和DENSE_RANK
的底层实现差异主要体现在相同排序键值的处理逻辑上,其核心流程可分为两个阶段:
-
数据分区(Shuffle阶段)
根据PARTITION BY
子句,通过MapReduce的HashPartitioner
将数据分发到不同Reducer。例如:ROW_NUMBER() OVER(PARTITION BY dept ORDER BY salary)
- 相同
dept
的记录会被分配到同一Reducer - 分区键的哈希值决定数据分发目标
- 相同
-
排序阶段(Sort阶段)
每个Reducer内部根据ORDER BY
子句进行全排序:- 使用快速排序或归并排序算法
- 内存不足时触发磁盘溢写(Spill to Disk)
- 最终生成全局有序数据流
二、源码核心实现
掌握这些底层原理,能更好地应对字节跳动等大厂的深度面试考察。实际开发中建议根据业务需求选择函数,如去重场景优先使用ROW_NUMBER
,排名展示则考虑DENSE_RANK
。
1. ROW_NUMBER源码(GenericUDAFRowNumber.java)
// 核心状态维护类
public static class RowNumberBuffer implements AggregationBuffer {
long counter; // 行号计数器
Object[] params; // 存储分区键和排序键
@Override
public void reset() {
counter = 1; // 每个新分区重置为1
params = null;
}
}
// 行号生成逻辑
public Object evaluate(AggregationBuffer agg) {
RowNumberBuffer rnb = (RowNumberBuffer) agg;
long retVal = rnb.counter;
rnb.counter++; // 每行无条件+1
return retVal;
}
关键点:
- 每个分区独立维护
counter
变量 - 无排序键值比较逻辑
- 类似工厂流水线计数器
2. RANK源码(GenericUDAFRank.java)
// 状态管理类
public static class RankBuffer extends AbstractRankBuffer {
long rank; // 当前排名
long count; // 相同值计数
@Override
void reset() {
super.reset();
rank = 1; // 初始排名
count = 1; // 相同值计数器
}
}
// 排名更新逻辑
protected void updateRank(RankBuffer rb) {
if (rb.currentRow != null) {
int cmp = compare(rb.currentRow, rb.lastRow); // 比较当前行与上一行
if (cmp != 0) {
// 排序键变化时
rb.rank += rb.count; // 跳跃更新排名
rb.count = 1; // 重置计数器
} else {
rb.count++; // 相同值计数+1
}
}
}
精妙设计:
- 通过
compare()
方法判断排序键是否变化 rank += count
实现跳跃式更新- 类似跳棋游戏的计分规则
3. DENSE_RANK源码(GenericUDAFDenseRank.java)
// 状态管理类
public static class DenseRankBuffer extends RankBuffer {
@Override
void reset() {
super.reset();
rank = 1; // 独立计数器
}
}
// 排名更新逻辑
protected void updateRank(RankBuffer rb) {
if (rb.currentRow != null) {
int cmp