简介:本资源是一个面向大数据初学者与Hadoop实践者的综合学习项目,聚焦分布式计算与存储核心能力训练,覆盖MapReduce编程、HDFS文件操作、ZooKeeper集群协调、Hive数据仓库建模与Web日志分析等典型应用场景。压缩包为30MB的ZIP文件,共89个文件,包含30个Java源码(含MapReduce作业与Hive工具类)、38个JAR依赖库(如hadoop-core-1.1.2.jar、zookeeper-3.4.5.jar、hive-exec-0.9.0.jar等)、13个CSV测试数据集(small.csv、pagerank相关矩阵等)及日志样本access.log.10,结构清晰,便于分模块编译运行与调试。已有1822人学习下载,项目基于Hadoop 1.x生态构建,配套完整依赖与配置文件(.classpath、.project、prefs等),开箱即可导入Eclipse运行,适合动手验证原理、理解组件协同机制,并支撑课程实验、毕业设计或岗位技能夯实。
1. Hadoop简单应用案例:不是跑通WordCount就叫“会用”,而是能从日志里挖出谁在凌晨三点反复失败登录
很多人学Hadoop,卡在伪分布式环境搭好、WordCount跑出hadoop: 3就以为“掌握了”。但真实产线里,你拿到的从来不是干净的hello world——是每天2TB的Nginx访问日志、是混着乱码和空行的用户行为埋点、是字段错位的订单流水、是压缩包套压缩包的离线报表。所谓“简单应用”,恰恰是指:在不依赖Spark/Flink、不重写YARN调度逻辑的前提下,仅用原生HDFS+MapReduce(或YARN上跑的更轻量级计算如DistCp、Streaming),解决一个具体、可验证、有业务反馈的小闭环问题。比如:统计某APP昨日各城市活跃用户数(去重IP+设备ID)、识别出异常高频请求的IP段(每分钟超500次)、把散落在12个目录下的CSV日志合并为分区表供BI工具直连。本文聚焦这类“小而实”的场景,不讲集群扩容、不碰Kerberos认证、不画高可用架构图,只带你用最朴素的Hadoop命令、Shell脚本和Java/Python MapReduce,把数据从“扔进HDFS”走到“生成一张能被业务方截图发邮件的Excel”。适合刚配通伪分布式、想验证自己真能干活的工程师,也适合需要快速交付轻量ETL任务的运维或数据同学。
2. 用原生Hadoop命令链完成日志清洗与轻量聚合:从上传到生成日报CSV
Hadoop的“简单应用”第一课,永远是绕不开HDFS操作与MapReduce基础流程。但重点不是背命令,而是理解数据在HDFS上的生命周期如何对应业务动作。下面以“分析昨日Nginx访问日志中的TOP10热门URL”为例,走一遍最小可行路径。注意:所有命令均在Hadoop伪分布式模式下验证(Hadoop 3.3.6 + JDK 11),无需修改core-site.xml或yarn-site.xml的HA配置。
2.1 准备原始日志并上传至HDFS:别跳过这一步,90%的后续失败源于路径或权限
假设你本地有一份access.log.20240520(约80MB,标准Nginx combined格式),需先确认HDFS目标路径存在且可写:
# 创建业务专用目录(按日期分区,养成习惯) hdfs dfs -mkdir -p /data/nginx/access/dt=20240520 # 上传前检查本地文件编码(避免GBK乱码导致Mapper解析失败) file -i access.log.20240520 # 应显示 charset=utf-8;若为gbk,先转码:iconv -f GBK -t UTF-8 access.log.20240520 > access_utf8.log # 上传(-put比-copyFromLocal更常用,且自动创建父目录) hdfs dfs -put access_utf8.log /data/nginx/access/dt=20240520/提示:
hdfs dfs -ls /data/nginx/access/dt=20240520/必须能看到文件,且-rw-r--r--权限中owner是你当前Linux用户。若报Permission denied,执行hdfs dfs -chown your_username:supergroup /data/nginx/access—— 这是伪分布式下最常见的权限坑,不是安全漏洞,是HDFS默认umask(022)导致新目录无写权限。
2.2 编写MapReduce程序提取URL并计数:用Java而非Streaming,因需精确控制字段切分
为什么不用Python Streaming?因为Nginx日志字段含空格、引号、方括号,正则切分易错,JavaString.split()配合Pattern更可控。以下是最简可行代码(保存为UrlCountMapper.java):
import org.apache.hadoop.io.*; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; import java.util.regex.Pattern; public class UrlCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { // 匹配Nginx combined日志的URL字段:双引号包围,中间非空格(忽略协议和参数,只取path) private static final Pattern URL_PATTERN = Pattern.compile("\"[A-Z]+\\s+([^\\s\"]+)\\s+HTTP"); private final Text outputKey = new Text(); private final IntWritable outputValue = new IntWritable(1); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString().trim(); if (line.isEmpty()) return; java.util.regex.Matcher m = URL_PATTERN.matcher(line); if (m.find()) { String urlPath = m.group(1); // 过滤掉/favicon.ico、/robots.txt等无业务价值路径 if (!urlPath.matches("/(favicon\\.ico|robots\\.txt|.*\\.(js|css|png|jpg|gif))")) { outputKey.set(urlPath); context.write(outputKey, outputValue); } } } }Reducer保持最简(IntSumReducer已内置):
import org.apache.hadoop.io.*; import org.apache.hadoop.mapreduce.Reducer; public class UrlCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private final IntWritable result = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } }2.3 编译打包并提交作业:关键在classpath和输入输出路径
# 1. 编译(假设HADOOP_HOME=/opt/hadoop) javac -cp "$HADOOP_HOME/share/hadoop/common/*:$HADOOP_HOME/share/hadoop/mapreduce/*" \ UrlCountMapper.java UrlCountReducer.java # 2. 打jar包(不包含Hadoop依赖,运行时由YARN提供) jar -cvf urlcount.jar UrlCount*.class # 3. 提交作业(指定输入路径、输出路径、主类) hadoop jar urlcount.jar \ -D mapreduce.job.name="nginx_url_top10" \ -D mapreduce.map.memory.mb=1024 \ -D mapreduce.reduce.memory.mb=1024 \ UrlCountMapper \ /data/nginx/access/dt=20240520/access_utf8.log \ /output/urlcount_20240520参数说明:
-D mapreduce.job.name:作业名,YARN Web UI中可见,便于定位;-D mapreduce.map.memory.mb:显式设置Mapper内存,避免伪分布式下因JVM堆不足被YARN Kill(默认512MB常不够);
输入路径必须是HDFS路径(/data/...),输出路径/output/...必须不存在,Hadoop会自动创建;
若报ClassNotFoundException: UrlCountMapper,检查jar包是否包含class文件(jar -tf urlcount.jar | grep UrlCount)。
2.4 提取结果并生成CSV:用HDFS命令+Linux工具链完成最后一步
MapReduce输出是多文件(part-r-00000,part-r-00001...),需合并并排序:
# 合并所有part文件到本地 hdfs dfs -getmerge /output/urlcount_20240520 /tmp/urlcount_result.txt # 按数值倒序排序(第二列是count),取TOP10,转为CSV(URL,count) sort -k2,2nr /tmp/urlcount_result.txt | head -10 | awk -F'\t' '{print "\"" $1 "\", " $2}' > report_20240520.csv # 上传回HDFS归档(业务方可能需要) hdfs dfs -mkdir -p /report/nginx/dt=20240520 hdfs dfs -put report_20240520.csv /report/nginx/dt=20240520/至此,从日志上传到生成可读CSV,全程未启动任何外部服务,纯Hadoop原生命令+自定义MR。这就是“简单应用”的实质:用HDFS做可靠存储,用MapReduce做确定性计算,用Shell做胶水逻辑。
3. 用DistCp实现跨集群/跨目录的增量同步:比rsync更稳,比Flume更轻
当“简单应用”升级为“日常运维”,你很快会遇到:测试集群要同步生产集群的用户画像表、每日ETL产出需归档到冷备HDFS、不同部门的HDFS命名空间要定期对齐。此时,hadoop distcp就是那个被低估的瑞士军刀——它不是MapReduce,而是基于MapReduce构建的分布式拷贝框架,专治大数据量、高容错、带校验的同步场景。
3.1 DistCp核心能力与适用边界:什么该用,什么不该用
DistCp不是万能的。它的优势在于:
- 断点续传:失败后可
-update或-diff只同步差异; - 带宽控制:
-m 20限制Mapper数,避免打爆网络; - 校验保障:
-skipcrccheck跳过CRC(提速),但默认开启,确保字节级一致; - 跨版本兼容:Hadoop 2.x集群可同步到3.x(需指定
-D fs.defaultFS=hdfs://...)。
但它不适合:
- ✘ 实时同步(毫秒级延迟)→ 选Kafka+Spark Streaming;
- ✘ 小文件极多(<1MB)→ DistCp会启大量Mapper,效率反低于
hdfs dfs -cp; - ✘ 需要字段级过滤或转换 → 先用MR处理,再DistCp。
血泪经验:曾用DistCp同步10TB数据,因未加
-m 50(默认Mapper数由源文件数决定),瞬间启了2000个Mapper,占满YARN队列,导致其他作业全部Pending。后来固定-m 50,耗时只增15%,但集群稳定度翻倍。
3.2 实战:每日同步生产集群的用户行为表到分析集群
假设生产集群地址hdfs://prod-nn:9000,分析集群hdfs://ana-nn:9000,需同步/data/user_behavior/dt=20240520到分析集群同路径:
# 1. 首次全量同步(加-log记录到HDFS,便于审计) hadoop distcp \ -log /distcp_logs/user_behavior_init_20240520 \ -m 30 \ hdfs://prod-nn:9000/data/user_behavior/dt=20240520 \ hdfs://ana-nn:9000/data/user_behavior/dt=20240520 # 2. 次日增量同步(-update只拷贝源有、目标无,或源更新时间新) hadoop distcp \ -update \ -log /distcp_logs/user_behavior_daily_20240521 \ -m 30 \ hdfs://prod-nn:9000/data/user_behavior/dt=20240521 \ hdfs://ana-nn:9000/data/user_behavior/dt=20240521关键参数说明:
-update:对比源/目标文件的修改时间(mtime)和大小,仅同步差异;-log <path>:将同步详情(成功/失败文件、耗时)写入HDFS,路径必须可写;-m 30:强制使用30个Mapper,避免小文件过多导致Mapper爆炸;
若源路径含通配符(如dt=202405*),DistCp会自动展开,但建议先hdfs dfs -ls确认。
3.3 处理常见同步失败:文件冲突、权限不足、网络抖动
DistCp失败日志通常藏在YARN ApplicationMaster日志里,但可通过以下三步快速定位:
- 看DistCp自身日志:
hdfs dfs -cat /distcp_logs/xxx/_logs/下的syslog; - 查YARN日志:
yarn logs -applicationId application_xxx; - 人工验证:
hdfs dfs -ls -R hdfs://prod-nn:9000/pathvshdfs dfs -ls -R hdfs://ana-nn:9000/path。
典型问题及解法:
| 现象 | 原因 | 解决 |
|---|---|---|
FileAlreadyExistsException: /target/file | 目标路径已存在且非空,-update无法覆盖 | 加-delete删除目标中源不存在的文件,或先hdfs dfs -rm -r /target清空 |
AccessControlException: Permission denied | 源集群HDFS开启了ACL,当前用户无读权限 | 在源集群执行hdfs dfs -setfacl -m user:your_user:r-x /source/path |
Connection refused或Timeout | 网络策略阻断了DataNode间通信(DistCp走DataNode直传) | 改用-direct参数(走NameNode中转,慢但稳),或检查防火墙开放50010端口 |
4. 避坑:Hadoop伪分布式与简单应用中5个高频翻车点
新手在跑通第一个WordCount后,常因环境细节栽跟头。这些坑不致命,但极其消耗调试时间。以下是我在3个不同客户现场记录的真实踩坑清单,按发生频率排序:
4.1 伪分布式下localhost:9000连不通,但127.0.0.1:9000可以
现象:hdfs dfs -ls /报Call From localhost/127.0.0.1 to localhost:9000 failed,但换成127.0.0.1就成功。
原因:core-site.xml中fs.defaultFS配置为hdfs://localhost:9000,而/etc/hosts中localhost映射到了::1(IPv6),Hadoop客户端优先尝试IPv6连接,但NameNode只监听IPv4。
解决:
- 方案1(推荐):改
core-site.xml为hdfs://127.0.0.1:9000; - 方案2:在
/etc/hosts中注释掉::1 localhost行,或添加127.0.0.1 localhost在::1之前。
4.2 MapReduce作业卡在ACCEPTED状态,YARN Web UI显示Application is Accepted and waiting for AM container
现象:hadoop jar xxx.jar后,yarn application -list看到状态为ACCEPTED,但数分钟不变成RUNNING。
原因:YARN资源不足,最常见是yarn.scheduler.maximum-allocation-mb(默认8192MB)大于NodeManager总内存,或yarn.nodemanager.resource.memory-mb未配置(默认8192MB),而你的机器只有4GB内存。
解决:
- 编辑
yarn-site.xml,添加:<property> <name>yarn.nodemanager.resource.memory-mb</name> <value>3072</value> <!-- 设为物理内存的75% --> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>3072</value> </property> - 重启YARN:
stop-yarn.sh && start-yarn.sh。
4.3hdfs dfs -put上传大文件时,NameNode日志报java.io.IOException: Failed to replace a bad datanode
现象:上传>1GB文件失败,NameNode日志出现Failed to replace a bad datanode,但hdfs dfsadmin -report显示DataNode正常。
原因:伪分布式下,DataNode和NameNode在同一台机器,dfs.datanode.du.reserved(默认0)未预留磁盘空间,当系统盘剩余<1GB时,DataNode拒绝写入。
解决:
- 在
hdfs-site.xml中添加:<property> <name>dfs.datanode.du.reserved</name> <value>1073741824</value> <!-- 预留1GB --> </property> - 重启HDFS:
stop-dfs.sh && start-dfs.sh。
4.4 自定义MapReduce程序读取HDFS文件时,java.lang.ClassNotFoundException: org.apache.hadoop.fs.FileSystem
现象:本地IDEA运行MR程序报ClassNotFoundException,但hadoop jar命令能跑。
原因:IDEA未将Hadoop的share/hadoop/common等目录加入Classpath,而hadoop jar命令会自动加载。
解决:
- IDEA中,Project Structure → Modules → Dependencies → + → JARs or directories,添加:
$HADOOP_HOME/share/hadoop/common/*$HADOOP_HOME/share/hadoop/common/lib/*$HADOOP_HOME/share/hadoop/hdfs/*$HADOOP_HOME/share/hadoop/mapreduce/* - 或更简单:在
main方法开头加System.setProperty("hadoop.home.dir", "/opt/hadoop");(Windows需下载winutils.exe)。
4.5 DistCp同步后,目标文件的修改时间(mtime)与源不一致
现象:hdfs dfs -ls /src/file和hdfs dfs -ls /dst/file显示mtime相差几秒甚至几分钟。
原因:DistCp默认不保留mtime(因HDFS不保证纳秒级精度),且文件复制过程本身有延迟。
解决:
- 若业务强依赖mtime(如下游ETL按mtime触发),改用
-update参数,它会校验mtime; - 或接受HDFS的最终一致性,用文件内容CRC(
hdfs dfs -checksum)代替mtime做一致性校验。
5. 用Hive on Tez加速SQL类简单应用:告别MapReduce的“编译等待”,让分析师当天提需求当天出数
当业务方说“能不能把昨天的用户地域分布导出来”,你还在写Java MR、编译、提交、等日志、合并结果……而隔壁组用Hive on Tez,打开Beeline,敲SELECT city, count(*) FROM user_log WHERE dt='20240520' GROUP BY city;,12秒出结果。这不是魔法,是Hadoop生态里最值得投入的“简单升级”——用SQL替代代码,用Tez替代MapReduce,零学习成本接入现有HDFS数据。
5.1 为什么Tez比MapReduce快?不是玄学,是执行模型的本质差异
MapReduce的瓶颈在“落盘”:Map输出必须写HDFS(或本地磁盘),Reduce再读取,中间至少两次IO。而Tez是DAG(有向无环图)引擎,允许Map输出直接内存传递给下一个Reduce(或Join),省去中间落盘。对简单聚合(如COUNT、SUM、GROUP BY),性能提升3~5倍是常态。
验证方式:同一SQL,在Hive on MR和Hive on Tez下分别执行,看YARN Application Timeline:
-- 在Hive CLI中执行(确保tez-site.xml已配置) SET hive.execution.engine=tez; SELECT COUNT(*) FROM nginx_access WHERE dt='20240520';注意:Tez需独立部署(
tez.tar.gz上传HDFS),但Hadoop 3.x已内置Tez支持,只需配置hive.execution.engine=tez。伪分布式下,Tez的AM(ApplicationMaster)和Task都在本机,无网络开销,加速效果更明显。
5.2 构建一个可复用的“日志分析”Hive数仓:3步搞定
Step 1:创建外部表,指向HDFS日志目录
-- 假设日志已按天分区存于 /data/nginx/access/dt=20240520/ CREATE EXTERNAL TABLE nginx_log ( ip STRING, time STRING, method STRING, url STRING, status STRING, size STRING ) PARTITIONED BY (dt STRING) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe' WITH SERDEPROPERTIES ( "input.regex" = "([^ ]*) - - \\[([^\\]]*)\\] \"([A-Z]*) ([^ ]*) HTTP/[^ ]*\" ([0-9]*) ([0-9]*)" ) LOCATION '/data/nginx/access/';Step 2:修复分区(让Hive感知到新分区)
-- 每日新增日志后执行 MSCK REPAIR TABLE nginx_log; -- 或手动添加分区(更精准) ALTER TABLE nginx_log ADD PARTITION (dt='20240520') LOCATION '/data/nginx/access/dt=20240520/';Step 3:用SQL完成复杂分析,无需写MR
-- 业务需求:找出昨日TOP5异常IP(404错误>100次) SELECT ip, COUNT(*) as cnt FROM nginx_log WHERE dt='20240520' AND status='404' GROUP BY ip HAVING cnt > 100 ORDER BY cnt DESC LIMIT 5;参数调优技巧:
SET tez.grouping.min-size=16777216;(16MB):避免小文件启太多Task;SET hive.tez.container.size=2048;:Tez Container内存,设为NodeManager内存的50%;SET hive.optimize.skewjoin=true;:自动处理数据倾斜(如某个IP占90%流量)。
5.3 与传统MR方案对比:一张表看清值不值得迁
| 维度 | 原生MapReduce | Hive on Tez |
|---|---|---|
| 开发效率 | 写Java/Python,编译打包,调试日志 | 写SQL,语法即逻辑,IDE自动补全 |
| 维护成本 | 每个新需求都要新写MR,版本管理混乱 | 复用同一套表结构,SQL即文档 |
| 执行速度(10GB日志) | 8~12分钟 | 1.5~3分钟(Tez DAG优化) |
| 资源占用 | Mapper/Reducer内存固定,易OOM | Tez动态分配Container,内存利用率高 |
| 学习门槛 | 需掌握Hadoop API、序列化、Combiner等 | 只需SQL基础,分析师可自助取数 |
我坚持在所有新项目中,默认启用Hive on Tez。不是因为它多炫酷,而是因为当业务方问“那个报表今天能出吗”,你不再需要解释“MR还在跑,预计20分钟”,而是直接说“正在执行,10秒后发你链接”。这种确定性,比任何技术指标都重要。
希望帮到你。
本文还有配套的精品资源,点击获取