大数据开发工程师笔试题
1,下面哪个程序负责 HDFS 数据存储?
a)NameNode
b)Jobtracker
c)Datanode
d)secondaryNameNode
e)tasktracker
2,下列哪个程序通常与NameNode 在一个节点启动?
a)SecondaryNameNode
b)DataNode
c)TaskTracker
d)Jobtracker
3,下列哪项通常是集群的最主要瓶颈
a)CPU
b)网络
c)磁盘 IO
d)内存
4,HBase 依靠什么存储底层数据
a) HDFS
b) Hadoop
c) Memory
d) MapReduce
5,LSM 含义是?
a) 日志结构合并树
b) 二叉树
c) 平衡二叉树
d) 长平衡二叉树
6,关于 HBase 二级索引的描述,哪些是正确的?
a) 核心是倒排表
b) 二级索引概念是对应 Rowkey 这个“一级”索引
c) 二级索引使用平衡二叉树
d) 二级索引使用 LSM 结构
7,解压.tar.gz 结尾的 HBase 压缩包使用的 Linux 命令是?
a) tar-zxvf
b) tar -zx
c) tar -s
d) tar –nf
8,请简述一下HDFS的体系结构。
9,如何使用MapReduce实现两个表join,可以考虑一下几种情况:(1)一个表大,一个表小(可放到内存中);(2)两个表都是大表?
10,insert into 和 override write区别?
11,flume不采集Nginx日志,通过Logger4j采集日志,优缺点是什么?
12,请简述一下Spark应用转换流程
13,Redis,传统数据库,hbase,hive 每个之间的区别是什么?
14,用mapreduce来实现下面需求?
现在有10个文件夹,每个文件夹都有1000000个url.现在让你找出top1000000url。
15,现有一个log文件,其中每一行格式如下(存在‘=’后面为空的情况,如第二行,需要转换成"null")
project_id=test&page_id=mobile_home&action=view&widget_id=mb_home_top_rec&content_version=1&country=pl&language=pl
project_id=test&page_id=mobile_home&action=view&widget_id=&content_version=1&country=pl&language=pl
请编写函数,读取该文本文件,并处理成RDD[Map]格式返回,函数格式如下,请编写{}中代码实现功能。
def processData(spark: SparkSession, readPath: String) = { }
16,
uid subject_id score
1001 01 90
1001 02 90
1001 03 90
1002 01 85
1002 02 85
1002 03 70
1003 01 70
1003 02 70
1003 03 85
请找出所有科目成绩都大于某一学科平均成绩的学生,编写SQL完成要求。
17,使用Java语言实现二分查找
update 2026.3.24
迟到几年的答案,来了
以下是针对您提出的大数据相关问题的准确答案:
1. 下面哪个程序负责 HDFS 数据存储?
答案:c) Datanode
- 解析:
- NameNode:负责管理文件系统的命名空间(元数据),如文件目录树、文件到数据块的映射等,不直接存储数据。
- DataNode:负责实际存储数据块(Block),执行客户端的读写请求。
- SecondaryNameNode:辅助NameNode合并编辑日志和镜像文件,不存储实际数据。
- JobTracker/TaskTracker:是MapReduce v1中的组件,负责计算任务调度,与HDFS存储无关。
2. 下列哪个程序通常与 NameNode 在一个节点启动?
答案:a) SecondaryNameNode
- 解析:在早期的Hadoop部署或伪分布式模式下,为了节省资源或便于管理,SecondaryNameNode 常与 NameNode 部署在同一台机器上(尽管在生产环境高可用架构中,它们通常分开部署,且SecondaryNameNode已被Standby NameNode取代,但在传统考题语境下,它是唯一与NameNode职能紧密相关且可能同节点的选项)。
- DataNode通常分布在从节点。
- JobTracker/TaskTracker属于计算框架,通常不与核心元数据节点混部以避免资源竞争。
3. 下列哪项通常是集群的最主要瓶颈?
答案:c) 磁盘 IO
- 解析:Hadoop生态系统设计用于处理海量数据,其核心操作是大量的读写。虽然网络带宽也是重要因素,但在大多数离线批处理场景中,磁盘的随机读写速度和吞吐量往往是限制整体性能的首要瓶颈(尤其是当数据量远超内存时)。
4. HBase 依靠什么存储底层数据?
答案:a) HDFS
- 解析:HBase是一个构建在HDFS之上的分布式列式数据库。它将数据存储在HFile中,而HFile最终作为文件存储在HDFS上。
5. LSM 含义是?
答案:a) 日志结构合并树
- 解析:LSM (Log-Structured Merge-tree) 是一种专为高写入吞吐量设计的数据结构,广泛应用于HBase、RocksDB、LevelDB等系统中。它通过将写入操作先追加到内存(MemStore)和日志(WAL),再定期合并到磁盘文件(SSTable)来优化写入性能。
6. 关于 HBase 二级索引的描述,哪些是正确的?
答案:a) 核心是倒排表; b) 二级索引概念是对应 Rowkey 这个“一级”索引; d) 二级索引使用 LSM 结构
- 解析:
- a) 正确:二级索引的本质是建立“列值 -> RowKey”的映射,这通常通过倒排表实现。
- b) 正确:HBase原生只支持通过RowKey查询(一级索引),二级索引是为了支持非RowKey列的查询。
- c) 错误:HBase底层存储(包括索引表)使用的是LSM树,而不是平衡二叉树。
- d) 正确:由于二级索引在HBase中通常体现为一张额外的表,这张表同样遵循HBase的存储机制,即使用LSM结构。
- (注:如果是单选题,最核心描述通常选 a 或 b,但 d 在技术原理上也是正确的。若为多选,则abd)
7. 解压.tar.gz 结尾的 HBase 压缩包使用的 Linux 命令是?
答案:a) tar -zxvf
- 解析:
-z: 处理gzip压缩。-x: 解压 (extract)。-v: 显示过程 (verbose)。-f: 指定文件名 (file)。
8. 请简述一下 HDFS 的体系结构。
答案:
HDFS 采用 Master/Slave 架构,主要包含以下组件:
- NameNode (Master):
- 管理文件系统的命名空间(目录树、文件权限等)。
- 维护文件到数据块(Block)的映射关系。
- 处理客户端的文件访问请求(打开、关闭、重命名等)。
- 不存储实际数据,元数据保存在内存和磁盘(FsImage + EditsLog)。
- DataNode (Slave):
- 负责实际存储数据块(Block)。
- 执行客户端发起的数据读写请求。
- 定期向NameNode发送心跳和块报告,汇报自身状态和数据块信息。
- SecondaryNameNode (辅助节点):
- 定期合并NameNode的编辑日志(EditsLog)和镜像文件(FsImage),防止日志过大,辅助检查点生成(注意:它不是NameNode的热备)。
- Client:
- 用户与文件系统交互的接口,负责与NameNode交互获取元数据,直接与DataNode交互读写数据。
9. 如何使用 MapReduce 实现两个表 join?
答案:
根据表的大小不同,主要有两种策略:
(1) 一个表大,一个表小(可放到内存中):Map 端连接 (Map-Side Join / Distributed Cache Join)
- 原理:利用Hadoop的分布式缓存(DistributedCache)将小表加载到每个Map任务的内存中(通常加载为哈希表)。
- 流程:
- 在Job配置中将小表添加到缓存。
- 在Map任务的
setup()方法中读取缓存文件,将小表数据加载到内存集合(如HashMap<Key, Value>)。 - 在
map()方法中读取大表的每一行,提取Join Key,直接在内存哈希表中查找匹配的小表记录。 - 如果找到匹配,直接输出结果。
- 优点:无需Shuffle过程,效率极高,无Reducer阶段。
(2) 两个表都是大表:Reduce 端连接 (Reduce-Side Join)
- 原理:利用MapReduce的Shuffle机制,将两个表中具有相同Key的记录发送到同一个Reducer进行处理。
- 流程:
- Map阶段:读取两个表的数据。对于每条记录,提取Join Key作为输出Key,Value部分需要标记来源(例如:
Tag=TableA或Tag=TableB)以及原始数据。 - Shuffle阶段:框架自动将相同Key的记录分发到同一个Reducer。
- Reduce阶段:接收一个Key和对应的Value列表(包含来自两个表的记录)。遍历列表,将数据按来源分组(如放入两个列表),然后进行笛卡尔积匹配(或根据具体逻辑匹配),输出Join后的结果。
- Map阶段:读取两个表的数据。对于每条记录,提取Join Key作为输出Key,Value部分需要标记来源(例如:
- 优点:通用性强,能处理任意大小的表。
- 缺点:涉及大量网络传输(Shuffle)和磁盘IO,性能相对较低;需处理数据倾斜问题。
10. insert into 和 overwrite write 区别?
答案:
这通常指 Hive 或类似数据仓库中的操作:
INSERT INTO:- 行为:追加数据。
- 结果:将新数据添加到目标表(或分区)的现有数据之后,保留原有数据。
- 场景:增量加载数据。
INSERT OVERWRITE(或LOAD DATA ... OVERWRITE):- 行为:覆盖数据。
- 结果:先清空目标表(或指定分区)中的所有现有数据,然后写入新数据。
- 场景:全量刷新数据、修正错误或重新计算某一分区。
11. Flume 不采集 Nginx 日志,通过 Log4j 采集日志,优缺点是什么?
(注:题目语境可能是指“应用日志通过Log4j直接发给Flume/下游”,对比“Flume直接读取Nginx文本日志文件”)
答案:
这里假设对比的是:方案A (Flume Exec/Tail源读取Nginx文本日志) vs 方案B (应用/Nginx模块通过Log4j/Socket直接发送日志给Flume/收集端)。
-
使用 Log4j (或 Appender 直接推送) 的优点:
- 实时性更高:日志产生后直接通过网络发送,无需等待文件刷盘或Flume轮询文件变化。
- 结构化好:可以在代码/配置层直接定义好字段格式,减少后续解析开销。
- 减少文件IO压力:减少了写磁盘再读磁盘的过程(如果完全不落盘)。
- 灵活控制:可以在应用层根据日志级别动态过滤,减少网络传输量。
-
使用 Log4j 的缺点:
- 耦合度高:日志采集逻辑侵入业务代码或应用配置,应用重启或配置错误可能导致日志丢失。
- 网络依赖强:如果收集端(Flume Agent)宕机或网络波动,应用端的日志发送可能阻塞应用线程(取决于Appender配置),影响业务性能。
- 缓冲能力弱:相比Flume的Channel机制,应用端的缓冲能力有限,容易在突发流量下丢数据。
- 维护成本:修改采集逻辑需要重启应用。
-
对比 (Flume 采集文件):解耦、可靠性高(有Channel持久化)、对应用无侵入,但实时性稍差(依赖文件滚动/轮询),且有额外的磁盘IO。
12. 请简述一下 Spark 应用转换流程。
答案:
Spark 应用的执行流程主要分为以下几个阶段:
- 构建 DAG (有向无环图):
- 用户编写代码,定义 RDD/DataFrame 及其转换算子(Transformation,如 map, filter, join)。
- Spark 记录这些依赖关系,形成逻辑执行计划(DAG)。此时不执行计算(惰性机制)。
- 提交作业 (Action):
- 当遇到行动算子(Action,如 count, collect, save)时,触发作业提交。
- DAGScheduler 介入:将 DAG 划分为多个 Stage(阶段)。划分依据是宽依赖(Shuffle Dependency),窄依赖则合并在一个Stage内。
- 任务调度:
- DAGScheduler 将 Stage 转化为 TaskSet(任务集),提交给 TaskScheduler。
- TaskScheduler 负责将任务分发到集群的 Executor 上执行。它会考虑数据本地性、资源情况等。
- 任务执行:
- Executor 接收 Task,在 JVM 线程池中运行具体的计算逻辑。
- 如果是 Shuffle 阶段,中间结果会写入本地磁盘,供下一个 Stage 拉取。
- 结果返回:
- 最终结果返回给 Driver 程序或写入外部存储系统。
13. Redis, 传统数据库, HBase, Hive 每个之间的区别是什么?
答案:
| 特性 | 传统数据库 (RDBMS, 如MySQL) | Redis | HBase | Hive |
| :— | :— | :— | :— :— |
| 数据模型 | 关系型模型 (行式存储),强Schema | Key-Value (支持多种数据结构),内存为主 | 列式存储 (宽表),稀疏数据 | 数据仓库 (基于HDFS的表结构),类SQL |
| 存储介质 | 磁盘 (配合内存缓存) | 内存 (持久化可选) | HDFS (磁盘) | HDFS (磁盘) |
| 主要用途 | OLTP (事务处理),复杂查询,强一致性 | 高速缓存,计数器,会话存储,消息队列 | 海量数据随机读写,实时查询,大数据OLTP | OLAP (离线分析),大规模数据批处理 |
| 扩展性 | 垂直扩展为主,水平分片较复杂 | 垂直扩展,集群模式支持水平扩展 | 天然水平扩展 (基于Region分裂) | 天然水平扩展 (基于HDFS) |
| 查询延迟 | 毫秒级 | 微秒/毫秒级 (极快) | 毫秒~秒级 (取决于扫描范围) | 秒~分钟/小时级 (高延迟) |
| 事务支持 | 支持完整 ACID | 支持单命令原子性,部分事务 | 支持单行原子性 | 不支持行级事务 (仅支持表级/分区级) |
| 查询语言 | SQL | 专用命令 | API (Thrift/Java), Phoenix (SQL) | HQL (类SQL) |
14. 用 MapReduce 实现 Top 1000000 URL (10个文件夹,每文件夹100万个URL)
需求:全局找出出现频率最高的 1,000,000 个 URL。
思路:这是一个典型的 “Top K” 问题,数据量较大(1000万条记录),不能直接在单机内存排序。
实现步骤:
-
Map 阶段:
- 输入:URL 列表。
- 逻辑:解析每一行,提取 URL。
- 输出:
<URL, 1>。 - (优化:如果数据量极大,可在Map端先做局部聚合,输出
<URL, Count>)
-
Combiner 阶段 (可选但推荐):
- 在Map端本地进行预聚合,减少网络传输量。
- 输入:
<URL, [1, 1, ...]>-> 输出:<URL, Sum>。
-
Reduce 阶段 (全局汇总):
- 输入:
<URL, [Count1, Count2, ...]>。 - 逻辑:累加得到每个 URL 的总频次
TotalCount。 - 难点:此时所有URL及其计数都在Reducer中,如果去重后URL数量依然巨大(接近1000万),直接排序可能内存溢出。
- 策略:
- 方法一 (单Reducer + 堆排序):如果去重后的URL数量在内存承受范围内(例如几百万),维护一个大小为 1,000,000 的最小堆。遍历所有
<URL, TotalCount>,若堆未满则插入;若已满且当前Count大于堆顶,则替换堆顶并调整堆。最后堆中即为Top 100w。 - 方法二 (二次 MapReduce / 桶分割):如果数据量太大,单个Reducer无法处理。
- 第一轮 MR:统计每个URL的总数
<URL, Count>。 - 第二轮 MR:
- Map:读取
<URL, Count>,根据 Count 的大小范围或者哈希将数据分发到不同的 Reducer(目的是让高频数据集中,或者均匀分布以便并行求TopK)。 - 更优策略:使用 TotalOrderingPartitioner 或者在Map端直接丢弃明显不可能进入前100w的小值(需预估阈值),或者每个Reducer求出本地的 Top 100w,最后在最后一个 Job 中合并这 N 个 Top 100w 列表(数据量变为 N*100w,很容易处理)。
- Map:读取
- 第一轮 MR:统计每个URL的总数
- 方法一 (单Reducer + 堆排序):如果去重后的URL数量在内存承受范围内(例如几百万),维护一个大小为 1,000,000 的最小堆。遍历所有
- 输入:
推荐方案 (两阶段):
- Job 1: 统计频次。Map:
(url, 1)-> Reduce:(url, sum)。输出文件为(url, count)。 - Job 2: 全局 TopK。
- Map: 读取 Job1 输出。
- Reduce: 设置 1个 Reducer (如果数据量允许) 或者 少量 Reducer。
- 如果在内存允许范围内(1000万个int+string对象约几百MB到1GB),直接用1个Reducer,使用最小堆维护Top 1,000,000。
- 如果内存紧张,使用多个Reducer,每个Reducer维护一个局部最小堆(比如局部Top 100w),输出后,再启动一个极小的Job将这些局部结果合并,再次用最小堆得出全局Top 100w。
15. 编写 Spark 函数处理日志转 RDD[Map]
需求:解析 key=value&key=value 格式,处理空值为 “null”。
import org.apache.spark.sql.SparkSession
import org.apache.spark.rdd.RDD
def processData(spark: SparkSession, readPath: String): RDD[Map[String, String]] = {
// 1. 读取文本文件
val rdd = spark.sparkContext.textFile(readPath)
// 2. 处理每一行
rdd.map(line => {
// 按 '&' 分割键值对
val pairs = line.split("&")
// 转换为 Map,并处理空值
pairs.map(pair => {
val kv = pair.split("=", -1) // -1 保证即使后面为空也能分割出两部分
val key = kv(0)
// 如果数组长度为1 (没有=) 或者 长度为2但值为空,则设为 "null"
val value = if (kv.length < 2 || kv(1).isEmpty) {
"null"
} else {
kv(1)
}
(key, value)
}).toMap
})
}
16. SQL 查询:找出所有科目成绩都大于某一学科平均成绩的学生
数据表结构:scores(uid, subject_id, score)
需求:找出学生,该学生的每一门科目的成绩,都大于该科目的平均成绩。
解题思路:
- 先计算每个科目 (
subject_id) 的平均成绩。 - 将原表与平均成绩表关联。
- 筛选出
score > avg_score的记录。 - 检查该学生是否所有科目都满足条件(即:该学生满足条件的科目数 == 该学生总共考的科目数)。
SQL 实现:
SELECT t1.uid
FROM scores t1
JOIN (
-- 1. 计算每个科目的平均分
SELECT subject_id, AVG(score) as avg_score
FROM scores
GROUP BY subject_id
) t2 ON t1.subject_id = t2.subject_id
-- 2. 筛选出成绩大于平均分的记录
WHERE t1.score > t2.avg_score
GROUP BY t1.uid
-- 3. 确保该学生所有科目都满足条件
-- 逻辑:满足条件的科目数量 = 该学生参加的总科目数量
HAVING COUNT(t1.subject_id) = (
SELECT COUNT(*)
FROM scores t3
WHERE t3.uid = t1.uid
);
另一种写法 (使用窗口函数,视数据库支持情况):
SELECT uid
FROM (
SELECT
uid,
subject_id,
score,
AVG(score) OVER (PARTITION BY subject_id) as avg_score,
COUNT(subject_id) OVER (PARTITION BY uid) as total_subjects
FROM scores
) t
WHERE score > avg_score
GROUP BY uid, total_subjects
HAVING COUNT(subject_id) = total_subjects;
我们下期见,拜拜!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)