大数据 — 小白讲解 + 面试题精解

大数据 — 小白讲解 + 面试题精解

定位:假设你只会写 SQL 单机查询,所有概念从零讲起,配代码示例 + 面试题。 覆盖:Apache Spark → Apache HBase 两大模块。 用法:先读讲解理解概念 → 跑代码验证 → 再看面试题自测。 特色:每个知识点都结合简历真实项目案例,面试时能直接用。


目录


第一章:Apache Spark(简历项目一资金拆分)

简历直接写了“将 Oracle 存储过程拆分规则转义为 Spark SQL 并行计算,拆分耗时 30+ 分钟降至 3 分钟”。 面试官会重点问:Spark 为什么快Shuffle 和数据倾斜


1.1 Spark 是什么(为什么比存储过程快)

小白讲解

一句话:Spark 是分布式内存计算引擎,把大数据拆成很多小块,分到多台机器并行算。

Spark 的核心抽象:

  RDD(弹性分布式数据集)
    最底层抽象,不可变的分布式数据集合
    可以理解为"分布在多台机器上的一个大数组"

  DataFrame(重点,简历用的这个)
    RDD 之上的结构化抽象,带列名和类型,类似"分布式的大表"
    支持 SQL 查询(Spark SQL)

  Spark SQL(简历直接写了)
    用 SQL 语法操作 DataFrame
    把 SQL 翻译成分布式执行计划

Spark 为什么比 Oracle 存储过程快?

  存储过程(串行):
    一个存储过程在数据库单节点上跑
    游标一行行处理 → 串行 + 磁盘 IO → 慢

  Spark SQL(并行):
    数据拆成 N 个分区 → N 个 Executor 并行计算
    中间结果尽量在内存 → 减少磁盘 IO
    → 30 分钟的任务 3 分钟跑完

结合简历案例

简历原文:
  "将原 Oracle 存储过程中的拆分规则转义为 Spark SQL 并行计算,
   设计合理分区策略避免 Shuffle 数据倾斜;
   拆分执行耗时从 30+ 分钟降至 3 分钟以内,日终清算窗口缩短 80%"

面试话术:
  "项目一的资金拆分逻辑原来写在 Oracle 存储过程里,
   用游标一行行遍历处理,千万级数据要跑 30 多分钟,
   严重影响日终清算窗口。
   
   我把拆分规则用 Spark SQL 重写:
   1. 游标循环 → Spark SQL 窗口函数(ROW_NUMBER / SUM OVER)
   2. 临时表 → Spark DataFrame
   3. 条件分支 → CASE WHEN
   
   并行化后,数据按指令类型、业务日期分区,
   多个 Executor 并行计算,30 分钟降到 3 分钟以内。"

1.2 RDD / DataFrame / Dataset

小白讲解

三个抽象的关系(从底层到高层):

  RDD(最低层)
    - 无类型,只有泛型 RDD[T]
    - 操作是函数式的(map/filter/reduce)
    - 适合底层优化,但写起来繁琐

  DataFrame(简历用的)
    - 有 schema(列名 + 类型),类似表
    - 可以写 SQL
    - 有 Catalyst 优化器自动优化

  Dataset(最高层,强类型)
    - DataFrame + 类型安全
    - Java/Scala 泛型

面试建议:说清楚"DataFrame 是带 schema 的 RDD,能写 SQL 且带优化器"

代码示例

// Spark SQL 资金拆分(简化版)
SparkSession spark = SparkSession.builder()
    .appName("FundSplit")
    .getOrCreate();

// 读取指令数据(从 HDFS 或 HBase)
Dataset<Row> instructions = spark.read()
    .format("jdbc")
    .option("url", "jdbc:gaussdb://...")
    .option("dbtable", "instruction")
    .load();

// 用 Spark SQL 实现拆分规则(替代 Oracle 游标逻辑)
instructions.createOrReplaceTempView("instruction");

Dataset<Row> splitResult = spark.sql(
    "SELECT " +
    "  instruction_no, " +
    "  biz_date, " +
    "  amount, " +
    "  -- 用窗口函数替代游标累计:按业务日期分区,按金额排序累计" +
    "  SUM(amount) OVER (PARTITION BY biz_date ORDER BY instruction_no) AS cumsum, " +
    "  -- 拆分成主从指令:金额超阈值拆主从" +
    "  CASE WHEN amount > 1000000 THEN 'PRIMARY' ELSE 'SUB' END AS split_type " +
    "FROM instruction " +
    "WHERE status = 'APPROVED'"
);

// 按拆分类型分区写入(合理分区避免 Shuffle 倾斜)
splitResult.repartition(200)  // 控制分区数
    .write()
    .mode("overwrite")
    .format("parquet")
    .save("/result/split");

面试题

Q1:RDD、DataFrame、Dataset 的区别?(高频 ⭐⭐⭐⭐)

答:

  • RDD:最低层抽象,无 schema,函数式操作,无优化器
  • DataFrame:带 schema(列名+类型)的分布式表,可写 SQL,有 Catalyst 优化器
  • Dataset:DataFrame + 强类型(编译期类型检查)
  • 简历用 DataFrame + Spark SQL(有 schema + 能写 SQL + 自动优化)

Q2:Spark 的惰性计算是什么?(中频 ⭐⭐⭐)

答:transformation(map/filter/select)不会立即执行,只是记录执行计划;遇到 action(count/collect/save)才真正触发计算。好处是 Spark 可以在执行前做整体优化(如合并多个 filter、裁剪不需要的列)。


1.3 Shuffle 原理(面试必问)

小白讲解

一句话:Shuffle = 数据在不同节点间重新分区(洗牌),是 Spark 最耗性能的操作。

什么时候触发 Shuffle?
  需要把不同分区的数据按某个 key 重新分组时:
  - groupBy / reduceByKey
  - join(两个表关联)
  - repartition(重新分区)
  - 窗口函数(PARTITION BY)

Shuffle 为什么慢?
  1. 磁盘 IO:中间数据要落盘(写入 shuffle 文件)
  2. 网络 IO:数据在节点间传输
  3. 数据倾斜:某个 key 数据特别多,全压到一个节点

Shuffle 的过程(以 reduceByKey 为例):
  ① Map 阶段:每个分区产出 (key, value)
  ② Shuffle 写:按 key 的哈希值分区,写到磁盘文件
  ③ Shuffle 读:Reducer 拉取自己负责的分区数据
  ④ Reduce 阶段:按 key 聚合

面试题

Q1:什么是 Shuffle?为什么耗性能?(高频 ⭐⭐⭐⭐)

答:

  • Shuffle 是数据跨节点重新分区的过程(按 key 哈希重新分布)
  • 耗性能因为:①中间数据落盘(磁盘 IO)②跨节点传输(网络 IO)③可能数据倾斜
  • 优化:减少 Shuffle 次数、用广播变量代替小表 join、合理分区

Q2:哪些操作会触发 Shuffle?(中频 ⭐⭐⭐)

答:groupBy/reduceByKey、join、repartition、distinct、窗口函数(PARTITION BY)。而 map/filter/select 这类窄依赖操作不触发 Shuffle。


1.4 数据倾斜(简历直接写了)

小白讲解

数据倾斜:
  数据按 key 哈希分区时,某个 key 的数据量特别大
  → 该 key 所在的分区任务特别慢(其他任务都跑完了,它还在跑)
  → 整体任务被拖慢(木桶效应)

举例:
  按指令类型分区,99% 的指令都是"普通"类型
  → "普通"类型的数据全在一个分区
  → 那个分区要处理 99% 的数据,其他分区处理 1%
  → 严重的负载不均

解决方案:
  1. 加盐(salting):
     给 key 加随机前缀,打散到多个分区
     (如 key 从 "普通" 变成 "普通_0"、"普通_1" ... "普通_9")
     聚合后再去掉前缀做二次聚合

  2. 自定义分区器:
     针对热点 key 单独处理

  3. 两阶段聚合:
     局部聚合(加盐)→ 全局聚合(去盐)

  4. 广播 join:
     小表用 broadcast,避免 shuffle join

结合简历案例

简历原文:
  "设计合理分区策略避免 Shuffle 数据倾斜"

面试话术:
  "资金拆分时发现某类指令(如普通转账)占了 99% 的数据量,
   按指令类型分区会导致严重倾斜——那个分区跑 10 分钟,
   其他分区 10 秒就完了。
   
   我做了两个优化:
   1. 加盐:对热点类型加随机后缀(普通_0 ~ 普通_9),
      把热点数据打散到多个分区,先局部聚合
   2. 去盐二次聚合:把加盐前缀去掉后再做全局聚合
   
   最终各分区负载均衡,整体耗时大幅下降。"

代码示例

// 加盐解决数据倾斜
Dataset<Row> instructions = spark.sql("SELECT type, amount FROM instruction");

// 1. 加盐:热点 key 加随机后缀
Dataset<Row> salted = instructions
    .withColumn("salt", functions.rand() % 10)  // 随机 0~9
    .withColumn("salted_key", functions.concat(functions.col("type"), 
        functions.lit("_"), functions.col("salt")))
    .groupBy("salted_key")
    .agg(functions.sum("amount").as("partial_sum"));

// 2. 去盐:去掉后缀,二次聚合
Dataset<Row> result = salted
    .withColumn("type", functions.split(functions.col("salted_key"), "_").getItem(0))
    .groupBy("type")
    .agg(functions.sum("partial_sum").as("total_amount"));

面试题

Q1:Spark 数据倾斜怎么解决?(高频 ⭐⭐⭐⭐⭐)

简历直接写了,必须会答!

答:

  1. 加盐(salting):热点 key 加随机前缀打散到多个分区,聚合后去盐二次聚合(简历方案)
  2. 自定义分区器:热点 key 单独处理
  3. 广播 join:小表广播到所有节点,避免 shuffle join
  4. 提高并行度:增加分区数(治标不治本)
  5. 预聚合:能预聚合的先聚合,减少 shuffle 数据量

Q2:怎么判断有没有数据倾斜?(中频 ⭐⭐⭐)

答:

  1. 看 Spark UI:某个 Stage 的某个 task 耗时远超其他 task
  2. 看 task 处理的数据量:某个 task 读取的数据量是其他的几十倍
  3. 看日志:某个 task 长期 RUNNING,其他 task 都 FINISHED 了

第二章:Apache HBase(简历项目一中间结果存储)

简历直接写了“拆分中间结果迁移至 HBase,设计 RowKey 前缀策略(业务日期+指令ID)支持范围查询”。 面试官会重点问:LSM 树原理RowKey 设计


2.1 LSM 树原理(简历直接写了)

小白讲解

一句话:LSM 树把随机写变成顺序写,通过“先写内存、再批量刷盘”大幅提升写入性能。

LSM 树 vs B+ 树(面试重点对比):

  B+ 树(MySQL InnoDB 用的):
    写入时:直接写磁盘上的数据页,随机 IO
    → 写入慢(每次写都要定位到磁盘位置)
    → 适合读多写少的场景

  LSM 树(HBase/LevelDB/RocksDB 用的):
    写入时:先写内存(MemStore),满了再顺序刷盘(HFile)
    → 写入快(顺序写,避免随机 IO)
    → 适合写多读少的场景

LSM 树的写入流程(面试要能画出来):

  ① 写请求 → 先写 WAL(Write-Ahead Log,预写日志,防丢失)
  ② 数据写入内存 MemStore
  ③ MemStore 达到阈值 → Flush 刷盘为 HFile(顺序写,很快)
  ④ HFile 越来越多 → Compaction 合并(整理碎片)

LSM 树为什么写入快?
  随机写 → 顺序写
  B+ 树每次写都要寻道(随机 IO,慢)
  LSM 树写内存 + 顺序刷盘(顺序 IO,快一个数量级)

结合简历案例

简历原文:
  "利用 HBase 的 LSM 树结构提升大批量写入性能"

面试话术:
  "资金拆分会产生海量中间结果(每天千万级),
   如果用 MySQL 存,随机写入性能扛不住。
   
   HBase 的 LSM 树结构把写入变成:
   先写内存 MemStore → 满了顺序刷盘为 HFile,
   顺序写比 MySQL 的随机写快一个数量级。
   
   所以我把拆分中间结果放到 HBase,大批量写入性能大幅提升。"

面试题

Q1:什么是 LSM 树?为什么写入快?(高频 ⭐⭐⭐⭐)

简历直接写了,必须会答!

答:

  • LSM 树(Log-Structured Merge Tree)用“顺序写”替代“随机写”
  • 写入流程:先写 WAL → 写内存 MemStore → 满了顺序刷盘为 HFile
  • 写入快的原因:B+ 树每次写都要随机寻道,LSM 树写内存 + 顺序刷盘,顺序 IO 比随机 IO 快一个数量级
  • 代价:读要查 MemStore + 多个 HFile,读性能不如 B+ 树(所以 HBase 适合写多读少)

Q2:LSM 树和 B+ 树的区别?(高频 ⭐⭐⭐⭐)

对比项 B+ 树(MySQL) LSM 树(HBase)
写入 随机写,慢 顺序写,快
读取 直接查,快 查多文件合并,慢
适用 读多写少 写多读少
代表 MySQL InnoDB HBase/LevelDB/RocksDB

2.2 RowKey 设计(简历直接写了)

小白讲解

RowKey 为什么重要?
  HBase 按 RowKey 的字典序存储数据
  相近的 RowKey 存在同一个 Region(数据分片)

好的 RowKey 设计能:
  1. 支持范围扫描(前缀相同的 RowKey 相邻)
  2. 避免热点(数据均匀分布到各 Region)

RowKey 设计原则:
  1. 长度:尽量短(减少存储和内存)
  2. 散列:避免用连续递增的时间戳/ID 做开头(会集中到一个 Region)
  3. 前缀:把查询常用的字段放前面,支持前缀范围扫描

简历的 RowKey:业务日期 + 指令ID
  RowKey = 20260115 + 00123456
  → 查询某一天的指令:scan 20260115 前缀,范围扫描
  → 业务日期放前面,因为查询都按日期过滤

结合简历案例

简历原文:
  "设计 RowKey 前缀策略(业务日期+指令ID)支持范围查询"

面试话术:
  "HBase 按 RowKey 字典序存储,我把 RowKey 设计成:
   业务日期(8位)+ 指令ID(8位)
   
   好处:
   1. 范围查询:查某一天的指令,scan '20260115' 前缀,
      直接范围扫描,不用全表扫
   2. 写入均匀:指令ID 是散列的,避免数据集中到一个 Region
   
   注意点:不能用时间戳做 RowKey 开头,
   因为时间戳连续递增会导致数据都写到一个 Region(热点问题)。"

面试题

Q1:HBase 的 RowKey 怎么设计?(高频 ⭐⭐⭐⭐⭐)

简历直接写了,必须会答!

答:三个原则:

  1. 长度短:减少存储和内存开销
  2. 散列分布:避免热点。不要用连续递增的时间戳/自增ID做开头,可以反转或加盐
  3. 前缀支持范围查询:把高频查询字段放前面(如业务日期+指令ID,scan 前缀即可范围查询)

Q2:HBase 热点问题(Hotspotting)是什么?(中频 ⭐⭐⭐)

答:如果 RowKey 开头是连续递增的(如时间戳),新写入的数据都会落到同一个 Region,导致该 Region 过载,其他 Region 空闲。解决:RowKey 加盐(随机前缀)、反转时间戳、或哈希打散。


2.3 HBase 架构与写入流程

小白讲解

HBase 架构:

  HMaster:管理 Region 的分配和负载均衡
  RegionServer:管理 Region,处理读写请求
  Region:数据分片(按 RowKey 范围划分)
  Store:一个列族的存储(MemStore + HFile)
  WAL:预写日志(Write-Ahead Log)

写入流程(面试必问):
  ① 写 WAL(先记日志,防数据丢失)
  ② 写 MemStore(内存)
  ③ MemStore 满了 → Flush 成 HFile(磁盘,顺序写)
  ④ HFile 多了 → Compaction 合并(整理碎片,删除过期数据)

面试题

Q1:HBase 的写入流程?(高频 ⭐⭐⭐⭐)

答:写 WAL → 写 MemStore(内存)→ Flush 刷 HFile(磁盘)→ Compaction 合并。WAL 保证数据不丢失(崩溃后可恢复),MemStore 保证写入快(先写内存),Flush 是顺序写(快),Compaction 清理碎片。

Q2:HBase 为什么适合大批量写入?(中频 ⭐⭐⭐)

答:LSM 树结构把写入变成“写内存 + 顺序刷盘”,避免了 B+ 树的随机写。适合写多读少的场景(如时序数据、日志、中间结果存储)。简历就是把资金拆分中间结果放 HBase,利用其高写入性能。


面试速查表(大数据)

考点 一句话答案
Spark 为什么快 分布式内存计算,多 Executor 并行
RDD vs DataFrame DataFrame 带 schema,能写 SQL,有优化器
Shuffle 数据跨节点重新分区,落盘+网络+可能倾斜
数据倾斜 热点 key 集中,加盐打散+二次聚合
LSM 树 顺序写替代随机写,写内存+顺序刷盘
LSM vs B+ 树 LSM 写快读慢,B+ 树读快写慢
RowKey 设计 短+散列防热点+前缀支持范围查询
热点问题 连续递增开头→集中一个 Region,加盐/反转解决
HBase 写入流程 WAL→MemStore→Flush→Compaction

最后更新:2026-08-19