Hadoop核心面试题大全¶
一、HDFS核心原理篇¶
题目1:请详细描述HDFS的读写流程¶
思路: - 分别阐述写入和读取流程 - 涉及NameNode、DataNode、Client的交互 - 说明数据块、副本、Pipeline等概念
参考答案:
写流程:
1. Client向NameNode发起写请求
2. NameNode检查权限和文件是否存在,返回是否可以写入
3. Client将文件切分成Block(默认128MB)
4. NameNode返回可用的DataNode列表(根据副本策略)
5. Client与第一个DataNode建立Pipeline连接
6. 第一个DataNode连接第二个,第二个连接第三个,形成Pipeline
7. 数据以Packet(64KB)为单位发送,采用流水线方式复制
8. DataNode逐级返回ACK确认
9. 所有Block写完后,Client通知NameNode关闭文件
读流程:
1. Client向NameNode发起读请求
2. NameNode返回文件Block列表及所在DataNode位置(按距离排序)
3. Client选择最近的DataNode读取Block
4. 读取完一个Block后,关闭与该DataNode连接
5. 继续读取下一个Block,直到文件读取完成
6. Client对读取的Block进行校验(Checksum)
题目2:NameNode的工作机制及元数据管理¶
思路: - 元数据存储结构 - EditLog和FsImage的作用 - 元数据持久化机制 - HA高可用实现
参考答案:
元数据结构:
// 核心数据结构
1. Namespace(目录树结构)
2. BlockMap(文件Block与DataNode映射)
3. DataNode注册信息
// 存储位置
- 内存:完整元数据(快速访问)
- 磁盘:FsImage + EditLog
持久化机制:
FsImage:元数据镜像文件,存储某一时刻的完整元数据
EditLog:编辑日志,记录每次元数据变更操作
启动流程:
1. 加载FsImage到内存
2. 回放EditLog中的操作
3. 生成新的FsImage
4. 清空EditLog
HA实现机制:
1. 两个NameNode:Active和Standby
2. 共享存储(JournalNode集群或NFS)
3. 只有Active对外服务
4. Standby实时同步EditLog
5. ZooKeeper实现故障自动切换(ZKFC)
题目3:HDFS的副本放置策略是什么?为什么这样设计?¶
思路: - 副本数量配置 - 放置规则 - 机架感知 - 设计考量
参考答案:
副本放置策略(默认3副本):
第一个副本:放在Client所在节点(如果Client在集群内)
或随机选择一个磁盘不太满、CPU不太忙的节点
第二个副本:放在与第一个副本不同机架的随机节点
第三个副本:放在与第二个副本相同机架的不同节点
设计原因:
题目4:HDFS小文件问题及解决方案¶
思路: - 小文件带来的问题 - 内存占用分析 - 解决方案
参考答案:
问题分析:
1. 每个文件/目录/Block在NameNode占用约150字节
2. 1亿个小文件需要约15GB内存
3. 启动时间长(需加载所有元数据)
4. MapReduce任务效率低(每个小文件一个Map任务)
解决方案:
// 1. HAR文件(Hadoop Archive)
hadoop archive -archiveName test.har -p /input /output
// 2. SequenceFile(键值对格式)
// 文件名作为Key,内容作为Value
Configuration conf = new Configuration();
SequenceFile.Writer writer = SequenceFile.createWriter(
conf, SequenceFile.Writer.file(path),
SequenceFile.Writer.keyClass(Text.class),
SequenceFile.Writer.valueClass(BytesWritable.class)
);
// 3. CombineFileInputFormat
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job, 128*1024*1024);
// 4. 源头解决:数据采集时合并小文件
// 5. Hive开启小文件合并
set hive.merge.mapfiles=true;
set hive.merge.mapredfiles=true;
二、MapReduce核心原理篇¶
题目5:请详细描述MapReduce的完整执行流程¶
思路: - 按阶段划分:Input -> Map -> Shuffle -> Reduce -> Output - 详细描述每个阶段的操作 - 涉及分区、排序、合并等
参考答案:
完整流程图:
InputFormat → Split → RecordReader
↓
Map Task
↓
Partitioner(分区)
↓
环形缓冲区(100MB,80%溢写)
↓
Spill(排序Sort + 合并Combiner可选)
↓
Merge(归并为一个文件)
↓
==========================================
Shuffle
==========================================
↓
Copy(Reduce拉取Map输出)
↓
Merge Sort(归并排序)
↓
Reduce Task
↓
OutputFormat → 写入HDFS
详细说明:
Map阶段:
1. InputFormat进行Split切片(逻辑切分)
2. 每个Split启动一个MapTask
3. RecordReader读取数据,转换为Key-Value
4. 用户Map逻辑处理,输出新的Key-Value
5. Partitioner决定数据去往哪个Reduce
Shuffle阶段(核心!):
1. Map输出写入环形缓冲区(kvbuffer,100MB)
2. 达到80%阈值触发Spill
3. Spill前进行分区内快速排序(QuickSort)
4. 可选Combiner本地聚合
5. 溢写文件归并成一个分区有序的大文件
6. Reduce主动拉取属于自己分区的数据
7. 边拉取边归并排序
Reduce阶段:
1. 对拉取的数据进行最终归并排序
2. 相同Key的Value形成迭代器
3. 用户Reduce逻辑处理
4. 通过OutputFormat写入HDFS
题目6:MapReduce的Shuffle机制详解¶
思路: - Shuffle是MapReduce的核心 - 分为Map端和Reduce端 - 详解每个环节
参考答案:
Map端Shuffle:
// 环形缓冲区结构
kvbuffer = 100MB(可配置mapreduce.task.io.sort.mb)
├── 数据区(顺时针写入)
├── 索引区(逆时针写入,每条16字节)
└── 赤道(equator,动态调整)
// Spill过程
1. 缓冲区达到80%(mapreduce.map.sort.spill.percent)
2. 启动Spill线程
3. 对数据区进行排序(先按Partition,再按Key)
4. 排序后写入磁盘临时文件
5. 可选执行Combiner
// 最终Merge
将多个Spill文件归并成一个大文件
每个Partition的数据连续存放
Reduce端Shuffle:
// Copy阶段
1. Reduce启动多个线程(默认5个)
2. 通过HTTP从Map节点拉取数据
3. 先放内存缓冲区,满了溢写磁盘
// Merge Sort阶段
1. 内存和磁盘数据统一归并
2. 最终形成Reduce的输入
3. 保证全局有序
优化参数:
<!-- Map端 -->
<property>
<name>mapreduce.task.io.sort.mb</name>
<value>100</value>
</property>
<property>
<name>mapreduce.task.io.sort.factor</name>
<value>10</value> <!-- 同时归并的文件数 -->
</property>
<!-- Reduce端 -->
<property>
<name>mapreduce.reduce.shuffle.parallelcopies</name>
<value>5</value> <!-- 拉取线程数 -->
</property>
题目7:MapReduce的Join实现方式有哪些?¶
思路: - 不同场景使用不同Join方式 - 大表Join大表 - 大表Join小表
参考答案:
1. Reduce Side Join(通用)
// 原理:Map打标签,Reduce合并
public class ReduceJoinMapper extends Mapper<...> {
@Override
protected void map(...) {
// 根据来源表打不同标签
if (来自订单表) {
outputValue.set("order#" + value);
} else {
outputValue.set("product#" + value);
}
context.write(joinKey, outputValue);
}
}
public class ReduceJoinReducer extends Reducer<...> {
@Override
protected void reduce(...) {
List<String> orders = new ArrayList<>();
List<String> products = new ArrayList<>();
for (Text value : values) {
if (value.startsWith("order#")) {
orders.add(value);
} else {
products.add(value);
}
}
// 笛卡尔积输出
for (String order : orders) {
for (String product : products) {
context.write(order + "\t" + product);
}
}
}
}
2. Map Side Join(小表Join大表)
public class MapJoinMapper extends Mapper<...> {
private Map<String, String> productMap = new HashMap<>();
@Override
protected void setup(Context context) throws IOException {
// 通过DistributedCache加载小表
URI[] files = context.getCacheFiles();
BufferedReader reader = new BufferedReader(
new FileReader(files[0].getPath())
);
String line;
while ((line = reader.readLine()) != null) {
String[] fields = line.split("\t");
productMap.put(fields[0], fields[1]);
}
}
@Override
protected void map(...) {
// 直接在Map端完成Join
String productInfo = productMap.get(joinKey);
if (productInfo != null) {
context.write(key, value + "\t" + productInfo);
}
}
}
// 驱动设置
job.addCacheFile(new URI("/cache/product.txt"));
3. SemiJoin(半连接)
// 适用场景:两张大表,但Join Key较少
// 思路:先获取Join Key集合,过滤后再Join
// 第一个MR:提取小表的所有Key
// 第二个MR:用Key过滤大表,形成小表
// 第三个MR:执行正常的Map Join
题目8:MapReduce的数据倾斜问题如何解决?¶
思路: - 数据倾斜的表现和原因 - 多种解决方案
参考答案:
倾斜表现:
原因分析:
解决方案:
// 方案1:Combiner预聚合
job.setCombinerClass(MyCombiner.class);
// 方案2:自定义Partitioner分散Key
public class CustomPartitioner extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key, IntWritable value, int numPartitions) {
// 对热点Key加随机后缀
if (isHotKey(key)) {
return (key.hashCode() + new Random().nextInt(10)) % numPartitions;
}
return (key.hashCode() & Integer.MAX_VALUE) % numPartitions;
}
}
// 方案3:两阶段聚合(加盐打散 + 去盐聚合)
// 第一阶段Map:Key加随机前缀
String newKey = random.nextInt(10) + "_" + originalKey;
// 第一阶段Reduce:局部聚合
// 第二阶段Map:去掉前缀
// 第二阶段Reduce:最终聚合
// 方案4:过滤倾斜Key,单独处理
// 方案5:调整Reduce数量,但可能治标不治本
三、YARN核心原理篇¶
题目9:请详细描述YARN的架构和工作流程¶
思路: - YARN的组件及职责 - 作业提交到执行的完整流程 - 资源调度过程
参考答案:
架构组件:
ResourceManager(RM):
├── Scheduler:资源调度器(不负责应用监控)
└── ApplicationManager:管理所有应用程序
NodeManager(NM):
├── 管理单个节点的资源
├── 启动和监控Container
└── 向RM汇报资源使用情况
ApplicationMaster(AM):
├── 每个应用一个AM
├── 向RM申请资源
├── 与NM协调Container执行Task
└── 监控Task运行状态
Container:
├── 资源抽象(CPU、内存等)
└── 执行具体任务
工作流程:
1. Client向RM提交应用程序
2. RM分配Container给ApplicationMaster
3. AM启动并向RM注册
4. AM根据需求向RM申请资源
5. RM分配Container给AM
6. AM通知NM启动Container
7. Container执行具体任务
8. 任务执行完毕,AM向RM注销
时序图:
Client RM AM NM
| | | |
|--1.Submit--->| | |
| |--2.Allocate-->| |
| | |--3.Register-->|
| |<--4.Request---| |
| |---5.Assign--->| |
| | |--6.Launch---->|
| | |<--7.Status----|
|<--8.Finish---|<--------------| |
题目10:YARN的资源调度器有哪些?区别是什么?¶
思路: - 三种调度器的特点 - 适用场景 - 配置方式
参考答案:
1. FIFO Scheduler(先进先出)
2. Capacity Scheduler(容量调度器,默认)
特点:
- 多队列,每个队列有固定容量
- 队列内FIFO
- 支持资源弹性(队列可借用空闲资源)
- 支持多租户
配置:capacity-scheduler.xml
<property>
<name>yarn.scheduler.capacity.root.queues</name>
<value>default,production,development</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.production.capacity</name>
<value>60</value>
</property>
<property>
<name>yarn.scheduler.capacity.root.development.capacity</name>
<value>40</value>
</property>
3. Fair Scheduler(公平调度器)
特点:
- 动态平衡资源分配
- 支持抢占(preemption)
- 支持权重配置
- 更适合多用户共享集群
配置:fair-scheduler.xml
<allocations>
<queue name="production">
<weight>3.0</weight>
<minResources>10000 mb, 10 vcores</minResources>
</queue>
<queue name="development">
<weight>1.0</weight>
</queue>
</allocations>
对比:
| FIFO | Capacity | Fair
----------|------|----------|------
多队列 | ❌ | ✅ | ✅
资源隔离 | ❌ | ✅ | ✅
资源共享 | - | 弹性借用 | 动态分配
资源抢占 | ❌ | ❌ | ✅
适用场景 | 测试 | 固定配额 | 灵活共享
题目11:YARN的Container资源隔离是如何实现的?¶
思路: - CPU隔离 - 内存隔离 - 底层实现机制
参考答案:
内存隔离:
// 物理内存限制
yarn.nodemanager.pmem-check-enabled=true
// 虚拟内存限制
yarn.nodemanager.vmem-check-enabled=true
yarn.nodemanager.vmem-pmem-ratio=2.1 // 虚拟内存=物理内存*2.1
// 超限处理:Kill Container
CPU隔离:
// 方案1:默认调度器(DefaultContainerExecutor)
- 不做真正隔离
- 只是逻辑划分
// 方案2:LinuxContainerExecutor + CGroups
yarn.nodemanager.container-executor.class=
org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor
yarn.nodemanager.linux-container-executor.cgroups.hierarchy=/hadoop-yarn
yarn.nodemanager.linux-container-executor.cgroups.mount=true
CGroups实现原理:
# CPU限制
/cgroup/cpu/hadoop-yarn/container_xxx/cpu.shares
# 内存限制
/cgroup/memory/hadoop-yarn/container_xxx/memory.limit_in_bytes
# 示例
echo 1024 > /cgroup/cpu/container_1/cpu.shares
echo 2147483648 > /cgroup/memory/container_1/memory.limit_in_bytes
四、Hadoop高可用与优化篇¶
题目12:HDFS HA的实现原理是什么?¶
思路: - HA架构组件 - 元数据同步机制 - 故障切换机制
参考答案:
架构组件:
+------------------+
| ZooKeeper集群 |
+--------+---------+
|
+--------+---------+
| ZKFC | (每个NN一个)
+--------+---------+
|
+-------------+-------------+
| |
+----+----+ +----+----+
| Active | | Standby |
| NN | | NN |
+----+----+ +----+----+
| |
+-----+ JournalNode +-----+
| 集群 |
+---------------+
元数据同步(共享存储方案):
// JournalNode集群方案(推荐)
1. Active NN写EditLog到JournalNode集群(过半写入成功)
2. Standby NN定期从JournalNode读取EditLog回放
3. Standby NN定期做Checkpoint
// NFS共享存储方案(不推荐)
1. EditLog写入共享存储
2. 有单点故障风险
故障切换(ZKFC):
// ZKFC职责
1. HealthMonitor:监控NN健康状态
2. ActiveStandbyElector:参与主备选举
3. 调用NN的状态转换方法
// 切换流程
1. Active NN故障
2. ZKFC检测到,释放ZK锁
3. Standby的ZKFC获得锁
4. 执行Fencing(确保旧Active不再服务)
5. Standby转为Active
// Fencing机制
- SSH登录旧Active执行kill命令
- 通过共享存储加锁
题目13:Hadoop的性能调优从哪些方面入手?¶
思路: - 系统层面 - HDFS层面 - MapReduce层面 - YARN层面
参考答案:
1. 系统层面:
# 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 调整文件描述符限制
ulimit -n 65535
# 关闭Swap
swapoff -a
# 网络优化
net.core.somaxconn = 32768
net.ipv4.tcp_max_syn_backlog = 8192
2. HDFS层面:
<!-- 增大Block大小(大文件场景)-->
<property>
<name>dfs.blocksize</name>
<value>268435456</value> <!-- 256MB -->
</property>
<!-- 增加DataNode处理线程 -->
<property>
<name>dfs.datanode.handler.count</name>
<value>20</value>
</property>
<!-- NameNode处理线程 -->
<property>
<name>dfs.namenode.handler.count</name>
<value>100</value> <!-- 建议: 20 * ln(集群规模) -->
</property>
<!-- 短路本地读取 -->
<property>
<name>dfs.client.read.shortcircuit</name>
<value>true</value>
</property>
3. MapReduce层面:
<!-- Map端环形缓冲区 -->
<property>
<name>mapreduce.task.io.sort.mb</name>
<value>512</value>
</property>
<!-- 启用压缩 -->
<property>
<name>mapreduce.map.output.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>
<!-- 推测执行 -->
<property>
<name>mapreduce.map.speculative</name>
<value>true</value>
</property>
<!-- JVM重用 -->
<property>
<name>mapreduce.job.jvm.numtasks</name>
<value>10</value>
</property>
4. YARN层面:
<!-- NodeManager资源配置 -->
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>65536</value>
</property>
<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>16</value>
</property>
<!-- 单个Container资源 -->
<property>
<name>yarn.scheduler.minimum-allocation-mb</name>
<value>1024</value>
</property>
<property>
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>16384</value>
</property>
题目14:如何判断HDFS集群是否健康?有哪些监控指标?¶
思路: - 关键监控指标 - 监控工具和方法 - 告警阈值设置
参考答案:
核心监控指标:
// 1. NameNode指标
CapacityUsed / CapacityTotal // 存储使用率(告警阈值:80%)
NumLiveDataNodes // 存活节点数
NumDeadDataNodes // 死亡节点数(>0 告警)
MissingBlocks // 丢失Block数(>0 严重告警)
UnderReplicatedBlocks // 副本不足Block数
CorruptBlocks // 损坏Block数
BlocksTotal // 总Block数
FilesTotal // 总文件数
// 2. DataNode指标
HeapMemoryUsage // 堆内存使用
DiskUsage // 磁盘使用率
XceiverCount // 数据传输线程数
DatanodeNetworkErrors // 网络错误数
// 3. JournalNode指标(HA模式)
Syncs // 同步操作数
NumTxns // 事务数
监控方法:
# 1. 命令行工具
hdfs dfsadmin -report
hdfs fsck /
# 2. JMX接口
curl http://namenode:50070/jmx?qry=Hadoop:*
# 3. 监控系统集成
- Prometheus + Grafana
- Ganglia
- Ambari
- Cloudera Manager
健康检查脚本示例:
#!/bin/bash
# HDFS健康检查脚本
# 检查NN是否存活
nn_status=$(hdfs haadmin -getServiceState nn1 2>/dev/null)
if [ "$nn_status" != "active" ] && [ "$nn_status" != "standby" ]; then
echo "CRITICAL: NameNode异常"
exit 2
fi
# 检查Dead DataNode
dead_dn=$(hdfs dfsadmin -report 2>/dev/null | grep "Dead datanodes" | awk '{print $3}')
if [ "$dead_dn" -gt 0 ]; then
echo "WARNING: ${dead_dn}个DataNode死亡"
exit 1
fi
# 检查Missing Blocks
missing=$(hdfs fsck / 2>/dev/null | grep "Missing blocks:" | awk '{print $3}')
if [ "$missing" -gt 0 ]; then
echo "CRITICAL: ${missing}个Block丢失"
exit 2
fi
echo "OK: HDFS集群健康"
exit 0
五、综合应用篇¶
题目15:请设计一个日志分析系统的技术方案¶
思路: - 需求分析 - 技术选型 - 架构设计 - 数据流转
参考答案:
架构设计:
日志采集层:
├── Flume Agent (部署在各业务服务器)
├── Source: TailDir (监控日志目录)
├── Channel: File/Kafka Channel
└── Sink: HDFS/Kafka Sink
消息队列层:
├── Kafka集群
├── 日志主题分区设计
└── 保留策略配置
存储层:
├── HDFS (原始日志存储)
│ ├── 按日期分区: /logs/dt=2024-01-01/
│ └── 使用LZO/Snappy压缩
└── Hive (数据仓库)
├── ODS层: 原始日志表
├── DWD层: 明细数据表
├── DWS层: 轻度汇总表
└── ADS层: 应用数据表
计算层:
├── 离线计算: MapReduce/Hive/Spark
├── 实时计算: Flink/Spark Streaming
└── 即席查询: Presto/Impala
应用层:
├── 数据可视化: Superset/Grafana
├── 告警系统: 自研或ElastAlert
└── 数据API: 对外提供查询服务
核心配置:
# Flume配置示例
agent.sources = taildir
agent.channels = kafka-channel
agent.sinks = hdfs-sink kafka-sink
# TailDir Source
agent.sources.taildir.type = TAILDIR
agent.sources.taildir.filegroups = f1
agent.sources.taildir.filegroups.f1 = /var/log/app/.*log
# Kafka Channel
agent.channels.kafka-channel.type = org.apache.flume.channel.kafka.KafkaChannel
agent.channels.kafka-channel.kafka.bootstrap.servers = kafka1:9092,kafka2:9092
agent.channels.kafka-channel.kafka.topic = logs
# HDFS Sink
agent.sinks.hdfs-sink.type = hdfs
agent.sinks.hdfs-sink.hdfs.path = /logs/dt=%Y-%m-%d/%H
agent.sinks.hdfs-sink.hdfs.filePrefix = log
agent.sinks.hdfs-sink.hdfs.rollInterval = 3600
agent.sinks.hdfs-sink.hdfs.rollSize = 134217728
agent.sinks.hdfs-sink.hdfs.rollCount = 0
Hive表设计:
-- ODS层日志表
CREATE EXTERNAL TABLE ods_log (
log_time STRING,
user_id STRING,
ip STRING,
url STRING,
referer STRING,
status INT,
response_time INT
)
PARTITIONED BY (dt STRING, hour STRING)
STORED AS ORC
LOCATION '/warehouse/ods/ods_log';
-- 日志解析ETL
INSERT OVERWRITE TABLE ods_log PARTITION(dt='${dt}', hour='${hour}')
SELECT
regexp_extract(line, '\\[(.*?)\\]', 1) as log_time,
regexp_extract(line, 'user_id=(\\w+)', 1) as user_id,
regexp_extract(line, '^(\\S+)', 1) as ip,
-- 更多字段解析...
FROM ods_log_raw
WHERE dt='${dt}' AND hour='${hour}';
题目16:Hadoop集群如何进行容灾设计?¶
思路: - 单点故障处理 - 机房级容灾 - 数据备份策略
参考答案:
1. 组件高可用:
NameNode HA:
├── 2个NameNode (Active + Standby)
├── JournalNode集群 (3或5节点)
├── ZooKeeper集群 (故障自动切换)
└── ZKFC (健康监控)
ResourceManager HA:
├── 2个RM (Active + Standby)
├── ZooKeeper选举
└── 状态存储在ZK
Hive Metastore HA:
├── 多个Metastore实例
└── 共享MySQL (主从复制)
2. 数据保护:
<!-- 多副本策略 -->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<!-- 机架感知 -->
<property>
<name>net.topology.script.file.name</name>
<value>/etc/hadoop/rack-awareness.sh</value>
</property>
<!-- 纠删码 (Hadoop 3.x) -->
hdfs ec -setPolicy -path /cold-data -policy RS-6-3-1024k
3. 跨机房容灾:
# DistCp数据同步
hadoop distcp \
-update \
-skipcrccheck \
-m 100 \
hdfs://cluster1/data \
hdfs://cluster2/data
# 定时同步脚本
0 */6 * * * /scripts/distcp-sync.sh >> /logs/sync.log 2>&1
4. 备份策略:
# NameNode元数据备份
#!/bin/bash
BACKUP_DIR=/backup/nn-metadata/$(date +%Y%m%d)
mkdir -p $BACKUP_DIR
# 备份FsImage
hdfs dfsadmin -fetchImage $BACKUP_DIR
# 备份到远程存储
aws s3 sync $BACKUP_DIR s3://backup-bucket/hadoop/nn/
# 保留最近30天
find /backup/nn-metadata -mtime +30 -delete
题目17:请解释Hadoop 3.x的新特性¶
思路: - 架构层面改进 - 性能提升 - 新功能
参考答案:
1. 纠删码(Erasure Coding)
# 减少存储开销,从3副本(200%开销)降到约50%
# 适用于冷数据
# 查看支持的策略
hdfs ec -listPolicies
# 设置纠删码策略
hdfs ec -setPolicy -path /cold-data -policy RS-6-3-1024k
# RS-6-3表示:6个数据块 + 3个校验块,可容忍任意3块丢失
2. NameNode Federation 2.0
3. 支持多NameNode
<!-- 支持2个以上NN,提高可用性 -->
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2,nn3</value>
</property>
4. 基于YARN的Timeline Service v2
5. 支持多种文件系统
# Azure Data Lake Storage
hdfs dfs -ls abfs://container@account.dfs.core.windows.net/
# Google Cloud Storage
hdfs dfs -ls gs://bucket/path
# S3A增强
hdfs dfs -ls s3a://bucket/path
6. JDK版本要求
7. 默认端口变化
NameNode HTTP: 50070 -> 9870
NameNode RPC: 8020 (不变)
DataNode HTTP: 50075 -> 9864
Secondary NN HTTP: 50090 -> 9868
题目18:生产环境中Hadoop集群规模如何规划?¶
思路: - 存储容量规划 - 计算资源规划 - 节点配置建议
参考答案:
存储规划:
公式:
所需HDFS容量 = 原始数据量 × 副本系数 × (1 + 数据增长率) × 安全系数
示例计算:
- 每天新增数据: 1TB
- 保留周期: 1年
- 副本数: 3
- 年增长率: 50%
- 安全系数: 1.3 (预留30%)
年数据量 = 1TB × 365天 = 365TB
HDFS容量 = 365TB × 3 × 1.5 × 1.3 ≈ 2.1PB
单节点12 × 8TB = 96TB可用空间
所需DataNode数 = 2.1PB / 96TB ≈ 22台
计算资源规划:
单NodeManager资源:
- 物理内存: 256GB,分配给YARN: 200GB
- CPU: 48核,分配给YARN: 40 vcores
单Container配置:
- 内存: 4GB
- vCores: 2
每节点可运行Container数 = 200GB / 4GB = 50个
如果日常运行100个并行任务:
所需节点数 = 100 / 50 × 1.5(峰值系数) = 3台
节点配置建议:
NameNode (2-3台):
├── CPU: 16核+
├── 内存: 128GB+ (元数据全内存)
├── 磁盘: SSD × 2 (系统 + JN)
└── 网络: 10Gbps
DataNode (按需):
├── CPU: 24核+
├── 内存: 256GB
├── 磁盘: HDD × 12 (8TB/块)
└── 网络: 10Gbps
管理节点 (ZK/JN):
├── CPU: 8核
├── 内存: 32GB
├── 磁盘: SSD × 2
└── 网络: 1Gbps
六、故障排查篇¶
题目19:HDFS常见故障排查及处理¶
思路: - 常见故障类型 - 排查思路 - 解决方法
参考答案:
1. NameNode启动失败
# 现象:NN启动后立即退出
# 排查步骤
1. 查看日志
tail -f /var/log/hadoop/hadoop-hdfs-namenode-*.log
# 常见原因及解决
# 原因1:元数据损坏
hdfs namenode -recover
# 原因2:磁盘空间不足
df -h
# 原因3:端口被占用
netstat -tlnp | grep 9870
# 原因4:FsImage/EditLog不一致(HA场景)
# 解决:从Standby复制元数据或使用备份恢复
2. DataNode无法注册
# 现象:DN启动但未在NN中显示
# 排查步骤
1. 检查防火墙
systemctl status firewalld
iptables -L
2. 检查主机名解析
ping namenode-hostname
3. 检查DN日志
tail -f /var/log/hadoop/hadoop-hdfs-datanode-*.log
4. 检查版本一致性
hdfs version
# 常见错误:Incompatible clusterIDs
# 解决:清理DN数据目录或复制VERSION文件
cat /data/hdfs/current/VERSION
3. Block丢失或损坏
# 检查Block状态
hdfs fsck / -files -blocks -locations
# 查看损坏文件
hdfs fsck / -list-corruptfileblocks
# 删除损坏文件
hdfs fsck / -delete
# 修复副本不足
hdfs dfsadmin -setReplication -R /path 3
# 强制触发副本复制
hdfs dfsadmin -triggerBlockReport datanode:50020
4. 空间不均衡
# 查看空间分布
hdfs dfsadmin -report
# 运行Balancer
hdfs balancer \
-threshold 10 \ # 偏差阈值
-policy datanode \ # 平衡策略
-exclude /exclude.txt # 排除节点
# 监控进度
tail -f /var/log/hadoop/hadoop-hdfs-balancer-*.log
题目20:MapReduce作业失败的常见原因及解决¶
思路: - OOM问题 - 数据倾斜 - 网络问题 - 代码问题
参考答案:
1. OOM错误
# Map端OOM
原因:单个Split数据量过大
解决:
mapreduce.input.fileinputformat.split.maxsize=67108864 # 减小Split
原因:Map输出缓冲区太小
解决:
mapreduce.task.io.sort.mb=512 # 增大缓冲区
原因:Map处理逻辑内存泄漏
解决:检查代码,避免在map()中累积大对象
# Reduce端OOM
原因:同一个Key的Value太多
解决:
mapreduce.reduce.shuffle.memory.limit.percent=0.25 # 减少Shuffle内存比例
或增加Reduce堆内存:
mapreduce.reduce.java.opts=-Xmx4096m
2. Container被Kill
# 日志关键字
"Container killed by the ApplicationMaster"
"Container killed on request"
# 排查
1. 检查是否超过内存限制
yarn.nodemanager.pmem-check-enabled=true
yarn.nodemanager.vmem-check-enabled=true
2. 增加Container内存
mapreduce.map.memory.mb=4096
mapreduce.reduce.memory.mb=8192
3. 读取HDFS超时
# 日志关键字
"Unable to read block"
"Connection timed out"
# 解决
1. 检查DN健康状态
hdfs dfsadmin -report
2. 增加重试次数
dfs.client.retry.max.attempts=10
dfs.client.socket-timeout=60000
4. 推测执行导致的问题
# 现象:同一个任务多次执行
# 关闭推测执行(如果任务有副作用)
mapreduce.map.speculative=false
mapreduce.reduce.speculative=false
总结:面试高频考点清单¶
| 类别 | 核心知识点 |
|---|---|
| HDFS | 读写流程、NameNode工作机制、副本策略、小文件问题、HA原理 |
| MapReduce | Shuffle机制、Join实现、数据倾斜解决、性能调优 |
| YARN | 架构组件、调度器对比、Container资源隔离、RM HA |
| 集群运维 | 监控指标、容量规划、故障排查、性能调优 |
| Hadoop 3.x | 纠删码、多NN支持、端口变化、S3A增强 |
以上题目覆盖了Hadoop面试的核心知识点,建议结合实际项目经验深入理解每个知识点的原理和应用场景。