尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Hadoop集群搭建与MapReduce开发实战:从规划到调优的完整指南

Hadoop集群搭建与MapReduce开发实战:从规划到调优的完整指南 简介面向大数据初学者和需要快速搭建Hadoop平台的技术人员这是一份Hadoop集群搭建与MapReduce程序开发的完整操作文档。文档基于VM虚拟机中Ubuntu Kylin 16.04.4环境涵盖SSH无密码登录、Java环境配置、Hadoop集群网络与分布式配置再到Eclipse开发环境集成、HDFS文件操作和MapReduce项目创建运行等环节每一步都附有可直接复制运行的代码和详细解释。资源共包含1个doc文件压缩包大小12.37MB文档结构清晰并专门整理了常见异常如NoRouteToHostException、Too many fetch-failures、OutOfMemoryError、DataNode未启动等的排错总结。已有1047人学习下载按步骤操作可有效降低Hadoop入门门槛帮助读者独立完成集群部署和MapReduce程序开发。1. 集群规划版本与拓扑这一步决定了后面百分之八十的坑很多人拿到Hadoop第一反应就是下载安装包、改配置、敲启动命令结果折腾一晚上不是这里报错就是那里起不来。我最早搭集群时也干过这事在伪分布式上跑通wordcount就觉得自己会了结果真到三台机器组集群光环境不一致导致的诡异问题就排查了两天。后来总结出一句话集群搭建里真正的技术含量不在敲命令而在动手之前的规划。1.1 版本选型Apache、CDH还是发行版别只图新版本选择是第一个关键决策点。目前主流选择有三类Apache原生社区版更新快、文档全完全开源免费适合愿意自己维护、想深挖原理的场景。但Apache版各组件版本之间完全自己匹配Hadoop 3.3.x配Hive 3.1.x配Spark 3.x哪个版本配哪个JDK出了兼容问题全靠自己啃官方文档。CDH/云厂商发行版组件间做了版本兼容验证自带管理界面适合企业级生产环境。CDH已经不再对非商业用户开放新版本所以现在很多团队直接转向云厂商的大数据套件或者用Ambari这样的工具做开源组件的可视化管理。编译过的第三方包网上有不少人分享了已经编译好、修复过坑的Hadoop二进制包。新人图省事用这个可以但我不建议生产环境依赖来路不明的包安全性和稳定性都没法保证。我的建议很直接如果你是自己学习和实验用Apache官方社区版版本选当前稳定的3.x系列比如3.3.x或3.4.x。原因有三一是社区版踩坑经验最多出任何问题都能搜到答案二是官方文档质量高方便你理解配置项背后的机制三是从生态兼容性看主流计算引擎都已经适配了Hadoop 3.x。1.2 节点角色规划小集群也要有清晰的职责边界规划节点时很多人会下意识地“每台机器都装所有组件”这样维护起来非常痛苦。正确做法是明确角色分工。以一个三节点集群为例我常用的划分方式是这样节点角色说明master1NameNode、ResourceManager、SecondaryNameNode主控节点负责元数据和资源调度master2备用NameNode可选、JobHistoryServer高可用场景使用实验环境可不配data1/data2DataNode、NodeManager数据存储和任务执行节点实验环境最简配置是“1主2从”主节点跑NameNode和ResourceManager从节点跑DataNode和NodeManager。如果你只有两台机器也可以让一台机器既当主控又当数据节点但生产环境千万别这么干NameNode挂掉等于全集群不可用和DataNode共置只会增加风险。还有个经常被忽略的点是磁盘分区规划。HDFS数据目录默认在/tmp下的话重启一次系统数据全没了。一定要把dfs.datanode.data.dir和dfs.namenode.name.dir指向独立挂载的磁盘目录最好还单独分区。我见过不止一个新手因为默认路径直接挂在根分区下日志一多直接把系统盘写满。2. 部署阶段最关键的环境细节JDK、SSH和那三个XML版本和拓扑定下来后进入实际部署。这一步看似机械但每一步都有讲究环境配置的差异是后面排查问题的最大干扰源。2.1 JDK版本、SSH免密和hosts映射三件容易被轻视的事先说说JDK。Hadoop 3.x要求JDK 8以上我实测过JDK 8和JDK 11都能跑但推荐统一用JDK 8。原因是Hadoop生态里的其他组件比如Hive、Spark的某些旧版本对JDK 11的支持还有坑团队里统一JDK版本能少很多沟通成本。安装时注意把JAVA_HOME写进/etc/profile或~/.bashrc每台机器都要配并且所有机器上的JDK路径尽量完全一致这样后面写脚本、配环境变量时不会出现路径不一致的问题。SSH免密登录这个环节顺序很关键在主节点执行ssh-keygen -t rsa生成密钥对一路回车即可。把公钥分发到所有节点包括主节点自己ssh-copy-id hadoopmaster1、ssh-copy-id hadoopdata1等。验证在master1上执行ssh data1能直接登录就算成功。这里有个经常踩的坑免密登录验证必须用你后续启动Hadoop的那个用户。如果你用root配置了免密但启动时用hadoop用户那照样连不上。还有每台机器的/etc/hosts必须配置完整的主机名映射包括所有节点的IP和主机名对应关系。如果在hosts里写的是master1但XML配置里写的是IP或者反过来启动时解析不一致就会报“Hostname cannot be resolved”之类的错误。我自己的习惯是统一用主机名因为IP变了的场景主机名不会变。2.2 core-site.xml、hdfs-site.xml、yarn-site.xml里真正要改的参数这三个配置文件是所有新手最晕的地方其实要改的参数就那么几个关键是理解每个参数在干什么。core-site.xml里最核心的是fs.defaultFS它指定了NameNode的地址和端口configuration property namefs.defaultFS/name valuehdfs://master1:8020/value /property property namehadoop.tmp.dir/name value/data/hadoop/tmp/value /property /configurationhadoop.tmp.dir这个参数特别重要如果没显式配置默认值会在/tmp下NameNode格式化生成的元数据就放在这里系统一重启全没了。这个参数决定了NameNode的数据存储根目录建议显式配置到数据盘。hdfs-site.xml里重点配置副本数和NameNode数据目录property namedfs.replication/name value2/value /property property namedfs.namenode.name.dir/name value/data/hadoop/namenode/value /property property namedfs.datanode.data.dir/name value/data/hadoop/datanode/value /property副本数默认是3但如果你只有两个DataNode设成3会导致部分副本无法写入虽然不会启动失败但会一直报块副本不足的告警。实验环境设成2就够了生产环境根据节点数量和可靠性要求来定。yarn-site.xml里核心是ResourceManager的地址和调度器配置property nameyarn.resourcemanager.hostname/name valuemaster1/value /property property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property /configurationyarn.nodemanager.aux-services这个参数非常容易漏它决定了NodeManager能跑MapReduce的Shuffle过程不配置的话MapReduce作业会卡在Map阶段100%但Reduce阶段一直不动。我最初就遇到过一次日志显示Shuffle failed排查半天才发现是少了这一项。另外提一句mapred-site.xml在Hadoop 3.x里这个文件默认不存在需要从模板复制或者直接在mapred-site.xml里配置MapReduce的框架property namemapreduce.framework.name/name valueyarn/value /property这段配置告诉MapReduce作业使用YARN作为资源调度框架而不是本地模式。很多教程会漏掉这个文件结果程序在本地跑而不在集群跑还误以为配好了集群。3. 启动与初始化格式化NameNode的时机错了元数据直接完蛋部署配置写完不是马上就能启动的启动前有一个操作让无数人翻过车——格式化NameNode。3.1 格式化到底做了什么什么时候才能做格式化NameNode本质上是初始化HDFS的元数据存储目录生成fsimage文件和VERSION文件等信息。你可以理解为给磁盘“分区并建文件系统”这个操作会清空NameNode目录下已有的所有元数据。所以执行hdfs namenode -format必须满足以下条件集群是第一次启动或者你明确要重建整个HDFS文件系统。所有DataNode节点上没有需要保留的数据格式化后会让DataNode的namespaceID和NameNode不一致导致DataNode启动后无法注册。有个经典翻车现场是集群已经跑了一段时间某天NameNode目录损坏或误删新手直接把NameNode格式化再启动结果DataNode全部拒绝注册因为DataNode数据目录里存有旧的namespaceID和新的NameNode不匹配。这时候数据还在DataNode磁盘上但元数据已经没了等于HDFS上的所有文件都找不回来。正确做法是先恢复元数据实在没办法只能接受数据丢失。3.2 启动顺序和健康检查别等日志刷屏才开始慌启动顺序也有讲究我的习惯是先在主节点启动HDFSstart-dfs.sh再在主节点启动YARNstart-yarn.sh最后启动HistoryServer可选mapred --daemon start historyserver启动后不要急着跑作业先做健康检查。用jps查看各节点的Java进程主节点应该有NameNode、SecondaryNameNode、ResourceManager从节点应该有DataNode、NodeManager如果某个进程缺失先看对应日志。Hadoop的日志路径一般在$HADOOP_HOME/logs/下启动过程的问题基本都能在hadoop-hadoop-namenode-主机名.log这样的文件里找到线索。健康检查再进一步的话用hdfs dfsadmin -report查看DataNode的在线状态以及hdfs dfs -mkdir -p /test测试能否正常创建目录。这些都通了再提交MapReduce作业否则作业失败你都不知道是集群问题还是代码问题。3.3 我实测最常见的两种启动失败场景第一种是NameNode起不来报错信息里出现“Incompatible namespaceIDs”或“java.io.IOException: NameNode is not formatted”。原因非常简单没有初始化NameNode目录。解决方式就是执行一次格式化但前提是确认DataNode没有需要保留的数据。第二种是DataNode进程起来后又立刻退出日志里出现“All specified directories are failed to load”。这种情况多半是dfs.datanode.data.dir配置的目录没有创建或者权限不对。注意Hadoop不会自动创建你指定的数据目录所以配置路径后要手动mkdir -p并确保启动用户有写权限。我第一次配置时就把路径写到了一个不存在的挂载点DataNode反复重启失败排查了大半天。4. MapReduce个性化开发模板代码到业务代码之间差的是这几点集群跑起来了下一步就是写MapReduce程序。网上教程满天飞的都是WordCount但现实中的需求哪有那么规整字段分隔符不统一、日志格式混乱、需要跨文件关联数据、输出格式要符合下游系统要求——这些才是日常开发真正要面对的问题。“个性化开发”四个字说的就是针对你的数据形态和业务逻辑对MapReduce框架做适配和定制。4.1 自定义Writable别再用Text硬扛复杂结构很多新手习惯把一条记录拼成字符串然后用Text传递整个对象。字段少的时候还行字段一多就难受了序列化效率低、代码可读性差、后面改字段还要改解析逻辑。更关键的是MapReduce的shuffle过程默认按key的排序规则来处理Text类型只有字典序一种排序方式遇到按数值字段排序就无能为力。自定义Writable接口是解决这类问题的标准做法。以最常见的日志解析为例假设你要从nginx日志里提取IP、访问时间、响应码和响应字节数那么可以定义一个LogRecordWritableimport org.apache.hadoop.io.Writable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; public class LogRecordWritable implements Writable { private String ip; private long timestamp; private int status; private long bytes; public LogRecordWritable() { } public LogRecordWritable(String ip, long timestamp, int status, long bytes) { this.ip ip; this.timestamp timestamp; this.status status; this.bytes bytes; } Override public void write(DataOutput out) throws IOException { out.writeUTF(ip); out.writeLong(timestamp); out.writeInt(status); out.writeLong(bytes); } Override public void readFields(DataInput in) throws IOException { this.ip in.readUTF(); this.timestamp in.readLong(); this.status in.readInt(); this.bytes in.readLong(); } // getter/setter... }写Writable时注意几点一是必须有空参构造器Hadoop在反序列化时要靠反射调用它二是readFields和write里的字段顺序必须完全一致三是如果字段有增删老数据文件会解析失败这就是HDFS上存了旧序列化数据时的兼容性问题尽量在业务上避免。4.2 Partition、Combiner和自定义InputFormat什么时候该用哪个跑通基础MapReduce之后下一层是理解框架的运作逻辑知道在哪个阶段能做什么事情。Partitioner决定key进哪个Reducer。默认是用key的hash值对reduce任务数取模数据分布不均匀时可以自定义Partitioner。比如日志分析按IP段分区让同一个IP段的访问统计落到同一个Reducer输出结果自然按段分开下游处理起来就方便。Combiner是Map端的“预Reduce”。它本质上是Reducer的一个特殊实现在Map输出后、落盘前先做一次局部合并减少shuffle的数据量。使用条件很关键Combiner的处理逻辑必须满足交换律和结合律比如求和、取最大值没问题但求平均值就不能直接用Reducer做Combiner否则结果完全不对。自定义InputFormat解决的是数据来源和解析方式的定制。默认的TextInputFormat按行切分key是行偏移量value是一行文本。如果你的数据是多行构成一条记录比如JSON跨行或者要从HBase、数据库里读数据就必须继承InputFormat自己实现RecordReader。有一个非常容易踩坑的点是Combiner不是包治百病的。用它之前一定要确认你的Reduce操作是“可以被并行合并的”。我之前写过一个统计用户平均访问时长的任务直接复用Reducer类当Combiner结果平均数的计算被重复合并了多次跑出来的结果差得离谱。后来改成在Map端输出“访问时长总和访问次数”的结构再在Reducer里做除法才得到正确结果。这类问题日志里不会报错只有做数据校验时才能发现结果不对尤其隐蔽。4.3 用完整例子讲一遍“个性化改造”的过程我拿一个实际做过的需求来说明。业务方给了一堆历史订单数据格式是order_id|user_id|sku_id|order_amount|order_date需求是按星期统计平均客单价。表面上看很简单固定套路就是Map解析、Shuffle按key聚合、Reduce算平均值。但上手后发现三个问题第一个问题分隔符是竖线默认的String.split(\|)可以处理但要小心连续分隔符导致的空字符串第二个问题order_amount是字符串形式的“¥123.45”带上货币符号后不能直接用parseDouble需要预处理第三个问题按星期统计枚举值只有7个默认HashPartitioner会导致一个Reducer处理所有相同key的数据虽然数据量不大但7个星期正好对应7个Reducer时需要自定义Partitioner确保每个星期的数据均匀分布。我的改造方案是自定义OrderWritable存放清洗后的数值金额和日期字段避免Map阶段反复解析字符串。Map阶段把“星期几”作为keyOrderWritable作为value输出。Reducer里累加金额和订单数最后输出平均值。自定义WeekPartitioner按key的hash值取模7让每个星期对应一个Reducer输出文件直接按星期拆开省去了后续二次加工。这个例子想表达的是“个性化开发”不是让你发明新框架而是在理解框架默认行为的基础上按业务语义定制每个阶段的行为。上面第1点是自定义Writable第2点是常规Map逻辑第3点是自定义Reducer逻辑第4点是Partitioner定制。整个开发量大头其实在前期的数据探查和格式清洗上代码本身不复杂。5. 提交运行、监控与调优程序真正跑起来之后才是战斗开始开发阶段在IDE里能跑通不代表在集群上能顺利运行。从本地单机到分布式集群环境差异会暴露出一堆之前想不到的问题。5.1 打包提交ClassNotFoundException是新手最常见的坎Hadoop MapReduce作业需要打成jar包提交但注意不要打成fat jar把所有依赖都打进去因为Hadoop集群本身已经有hadoop-common、hadoop-mapreduce-client-core这些依赖你打进去反而可能版本冲突。推荐的方式是在Maven里把hadoop依赖的scope设为provided这样打包时不会包含Hadoop自身的库。提交命令的通用形式是hadoop jar /path/to/your-job.jar com.example.LogAnalysisDriver \ -D mapreduce.job.reduces7 \ /input/orders.txt /output/weekly_avg这里有两个实战经验第一输出目录不能已存在。HDFS上的输出目录如果已存在提交时会直接报FileAlreadyExistsException。我的习惯是在Driver代码里先检查输出路径是否存在存在就提示或自动删除省得反复手动清理。第二提交后再加参数。在代码里把输入输出路径写成固定值是最糟糕的实践应该用ToolRunner解析命令行参数这样同一个jar包可以处理不同输入路径不需要重新编译。参考Apache的示例代码实现Tool接口重写run方法这样以后再接新的数据源只是敲一条命令的事。5.2 数据倾斜和内存溢出两个高频生产事故的识别与应对任务提交后在YARN的ResourceManager页面上可以看到作业执行情况。如果发现某个Reducer跑了很久还没结束而其他Reducer早已完成大概率是数据倾斜。数据倾斜的经典场景是key分布不均匀比如热点用户、热门商品。应对手段有几个由简到难增加Reducer数量可能是Reducer太少导致单个Reducer扛太多数据。调整mapreduce.job.reduces参数。加Combiner先做局部合并减少shuffle传输量。两阶段聚合加盐在Map输出的key上加一个随机前缀让同一个真实key的数据先分散到不同Reducer做局部聚合第二阶段再去掉前缀做全局聚合。这种方式适合聚合类操作但会增加一轮作业简单任务没必要这么重。自定义Partitioner根据业务分布不均匀的特征设计分区策略本质上是让数据按照期望的方式均匀分散。内存溢出OOM是另一个高频问题。Map阶段报OOM常见原因是单个Map处理的数据量太大或者Map输出buffer设置不当。调优时关注这几个参数参数默认值调优建议mapreduce.map.memory.mb1024Map任务内存上限数据量大时提到2048mapreduce.reduce.memory.mb1024Reduce任务内存上限mapreduce.map.java.opts-Xmx819mJVM堆大小通常设为memory.mb的75%~80%mapreduce.reduce.java.opts-Xmx819mReduce端JVM堆大小我在实际项目里有个体会调参前先看监控不要上来就加内存。如果Map任务报的是GC overhead limit exceeded那说明堆内存确实不够加大Xmx即可但如果报的是物理内存不足可能需要降低单任务内存提高容器数量来并行处理。盲目加内存不仅浪费资源还可能拖垮整台机器。5.3 我常用的几个排查手段和习惯调试MapReduce任务纯看日志效率非常低。我的习惯是先看YARN Web UI上每个任务的状态和日志入口比直接翻集群日志快得多。用yarn logs -applicationId appid拉取某个Application的完整日志重点搜“ERROR”“Exception”关键字。在Map和Reduce代码里适当用System.err.println输出中间结果这些信息会进入任务日志方便定位逻辑问题。注意别输出太大量否则日志文件会被刷爆。跑批任务时先用一小部分抽样数据验证逻辑再放全量数据。我见过有人在几千万条的输入上跑一个逻辑错误的任务跑了两小时后才发现结果全是垃圾白白浪费计算资源。最后分享一个看起来不起眼但很实用的建议提交作业的机器上保持Hadoop客户端的版本和集群一致。我遇到过开发本地装了Hadoop 2.7连接3.x集群结果rpc协议不兼容报了一堆莫名其妙的问题后来统一客户端版本后一切正常。这类环境一致性细节往往不在教程里但实际部署中出问题的频率非常高。本文还有配套的精品资源点击获取
返回列表