跳转至

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不太忙的节点

第二个副本:放在与第一个副本不同机架的随机节点

第三个副本:放在与第二个副本相同机架的不同节点

设计原因:

1. 可靠性:不同机架防止机架级故障
2. 写入性能:两个副本在同一机架,减少跨机架流量
3. 读取性能:均匀分布,增加读取并行度
4. 带宽均衡:跨机架写入只需穿越一次机架交换机


题目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. 大部分Task快速完成,个别Task长时间运行
2. 某些Reduce内存溢出
3. 任务进度卡在99%

原因分析:

1. Key分布不均匀(如:大量null或空值)
2. 业务数据本身倾斜(如:热门商品订单多)
3. 不合理的Partition设计

解决方案:

// 方案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

支持多NameNode水平扩展
每个NN管理部分Namespace
通过Router-based Federation简化客户端访问

3. 支持多NameNode

<!-- 支持2个以上NN,提高可用性 -->
<property>
    <name>dfs.ha.namenodes.mycluster</name>
    <value>nn1,nn2,nn3</value>
</property>

4. 基于YARN的Timeline Service v2

- 更好的可扩展性
- 使用HBase作为后端存储
- 支持流式写入

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版本要求

最低要求JDK 8
推荐JDK 11
部分特性需要JDK 17

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面试的核心知识点,建议结合实际项目经验深入理解每个知识点的原理和应用场景。