
1. Hadoop问题排查总体思路接手Hadoop集群的运维和问题排查算起来也有几年时间了。从最初搭三台机器做测试环境到后来管理几十个节点的生产集群踩过的坑写满过好几个笔记本。这个hadoop问题小结不是从哪本官方文档里抄来的而是我把这些年实际遇到、实际解决过的典型问题整理成的一份排错速查性质的笔记。如果你正在搭建集群或者你的集群正在抽风这份小结应该能帮你省下不少排查时间。先说一个核心观点Hadoop本身并不复杂复杂的是它依赖的分布式环境。绝大多数看起来诡异的问题归根结底逃不出几类——网络不通、时钟不同步、配置不一致、资源不足、版本不兼容。记住这个大方向排查时就不会像无头苍蝇一样乱转。我在实际工作中发现大概有七成所谓的疑难杂症最后定位到根因时都是非常朴素的原因比如某台机器磁盘写满了又比如某个配置项在三个配置文件里值不一致。所以我的排查套路一般分三步先看日志再看监控最后才翻配置。日志是Hadoop告诉你它哪里疼的方式监控是它告诉你哪里快要疼的方式配置则是你告诉它该怎么干活的方式。顺序搞反了就容易在错误的方向上浪费时间。比如DataNode启动失败如果直接去改配置很可能越改越乱正确的做法是先打开日志文件看一眼异常堆栈往往几十秒就能知道问题出在哪里。另外要养成一个习惯任何操作之前先确认当前目录、当前用户、当前环境变量。我曾经碰到过有人在一个节点上手动执行了格式化命令结果把整个集群的元数据清空的情况原因就是他在NameNode那台机器上误操作了。分布式系统的操作是不可逆的这条红线必须时刻绷紧。2. 部署与配置阶段的典型问题2.1 版本搭配引发的兼容性暗坑Hadoop生态的版本关系就像装修房子时的水电改造前期没规划好后期到处返工。这个问题的核心在于Hadoop主版本、JDK版本、操作系统版本三者的兼容矩阵不是随便搭配就能跑的。很多新手上来就下载最新的Hadoop版本配合最新的JDK结果启动时直接报UnsupportedClassVersionError或者各种莫名其妙的native方法找不到。我在实践中踩过一次很深的坑某次新搭建集群统一选了较新的Hadoop版本JDK也选了大版本里的最新小版本结果NameNode启动时频繁报出关于netty和native压缩库的异常。后来查了一整天才发现新版本Hadoop某些组件对JDK的特定小版本有隐含要求换成官方推荐的JDK小版本之后问题立刻消失。所以这里给三条实操建议优先参考发行版自带的版本兼容性说明不要凭感觉选版本集群内所有节点的JDK版本必须完全一致包括小版本号不要在已经跑稳定了的集群上升级JDK大版本除非你有十足的把握和完整的回滚方案另外注意区分开源版本和商业发行版。有些商业发行版对底层组件的版本做了封装和集成踩坑概率低一些但如果没有购买服务出了问题反而更难排查因为社区文档对发行版内部的改动未必覆盖全面。我的经验是生产环境要么用纯开源版本自行管理版本要么用商业发行版并购买配套支持最忌讳的是混搭。2.2 SSH免密与启动方式的各种状况SSH免密配置是搭建Hadoop集群时一个非常基础却又极容易出错的环境。它的原理很简单把公钥分发到目标机器的authorized_keys文件里。但实际操作中会遇到各种奇怪的情况比如明明配置了免密执行start-dfs.sh时还是提示输入密码或者脚本在某一台机器上卡住。这类问题我总结出几个高频原因操作系统是较新版本时SSH默认禁用了RSA公钥算法需要在sshd_config里显式开启公钥文件的权限不对authorized_keys的权限不能超过600否则SSH出于安全考虑会直接忽略用户主目录的权限如果是777同样会导致SSH拒绝读取公钥集群中主机名解析不统一有些节点写的是IP有些节点写的是hostname排查技巧先手动执行一遍ssh命令观察有没有报错信息。如果免密已经生效再检查启动脚本中是否存在对特定用户的依赖。Hadoop的启动脚本本质上都是通过SSH远程执行命令任何一台机器SSH不通整个启动流程就会卡在那里。还有一点容易被忽视集群时钟同步。分布式系统对时间同步的依赖远超很多人的想象。Kerberos认证、HDFS租约、任务超时判断这些都是基于时间的。某次我在实际运维中发现有一台节点的时间比集群标准时间慢了几分钟结果这台机器上的DataNode每隔一段时间就被判定为超时频繁进入宕机状态。搭好集群之后第一件事就是用crontab配好NTP同步现在主流的发行版大多自带chrony或ntpd服务记得检查它们的运行状态。2.3 配置文件不一致是最隐蔽的杀手Hadoop集群的配置管理本质上是多文件多节点的状态一致性问题。core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml这几个文件的配置项必须对所有节点保持一致否则你永远猜不到某个行为是哪个配置在起作用。举一个实际案例某次集群扩容新加的几台节点在启动DataNode时报错无法连接NameNode。排查了很久发现新节点的core-site.xml里写的fs.defaultFS还是原先测试环境的地址而旧节点早已改成了生产环境的地址。新旧节点配置文件不一致导致新节点根本不知道往哪里注册。这就是配置漂移问题。我的建议是把集群的配置管理纳入版本控制建立一套配置文件分发机制确保每次修改都能同步到所有节点。哪怕不用自动化工具至少要做到在NameNode上统一保存一份标准配置每台节点修改完配置后做一次对比校验。还要提醒一点修改配置后如果只执行了refresh命令有些配置项是生效不了的必须重启对应服务。比如修改了NameNode的堆内存大小光是refresh没用必须重启NameNode进程。这个修改—分发—确认—重启的完整链条每一步都不能偷懒。3. HDFS存储层的高频故障3.1 DataNode存活状态异常DataNode是HDFS里干活的主力也是最容易出问题的组件。常见的现象是某个DataNode在管理界面上显示为Dead或者数据块复制任务一直在pending状态。遇到这种情况第一步不是着急重启而是登录到对应的机器上看日志。DataNode的日志通常记录了它尝试连接NameNode失败的具体原因。我遇到过的情况包括防火墙拦截了数据传输端口、DataNode进程虽然活着但线程池已经卡死、磁盘故障导致写入失败累计到阈值。这个阈值问题是比较经典的DataNode对同一个磁盘的写入失败次数超过设定值之后会把该磁盘标记为坏盘并主动退出从而保护其他磁盘的数据安全。如果你看到日志里有关于磁盘故障的异常信息不要只想着重启进程应该先检查磁盘的实际健康状态。还有一个常见误操作把DataNode的存储目录配置在系统盘或者挂载点不稳定的路径上。生产环境中数据目录应该单独规划分配独立的磁盘不要和系统日志、临时文件混在一起。我曾经见过一台机器因为日志过大把数据目录所在分区写满导致整个DataNode崩溃集群自动把该节点上的副本复制到其他节点白白消耗了大量网络带宽。3.2 磁盘容量与数据均衡HDFS的数据均衡问题本质上是一个存储分布不均的问题。新节点加入集群时它默认是空的而旧节点已经存了大量数据。如果没有做数据均衡新节点就处于闲置状态整个集群的存储利用率大打折扣。balancer工具是Hadoop自带的均衡器使用起来很简单但有几点需要注意均衡操作会占用网络带宽和磁盘IO尽量在业务低峰期执行默认的均衡阈值是10%如果希望更激进可以调高但过高会影响在线业务均衡过程中如果集群出现故障优先处理故障均衡任务可以中断后续跑我在实践中发现除了节点间的数据均衡节点内的磁盘间均衡同样值得关注。当一台节点挂了多块盘时写入策略默认可能只会往某一两块盘上写长期下来会出现同一节点内磁盘使用率差距很大的现象。可以通过配置DataNode的磁盘选择策略来缓解但更直接的办法是定期检查各节点磁盘使用率发现偏差大时及时补充磁盘或者下线数据量大的磁盘。3.3 小文件问题与治理实操小文件问题可以说是HDFS存储体系里最经典的性能杀手。要理解它为什么是大问题得先明白HDFS的元数据管理机制NameNode把整个文件系统的元数据放在内存中每个文件、目录和数据块都需要在内存中占一条记录。当集群里的文件数量达到百万甚至千万级别时NameNode的堆内存在GC压力和容量压力下迅速吃紧整个集群的读写性能随之大幅下滑。我在负责一个日志存储型业务的时候就吃过小文件的亏。原本业务的日志是按分钟生成的一天下来产生了几万个文件而且文件本身只有几百KB大小。集群跑了一个月后NameNode开始频繁进行较长的GC暂停读写请求延迟明显增加。后来我们做了三件事写路径上合并让业务侧把按分钟的日志聚合到小时级别再写入HDFS读路径上归档使用方式对已有小文件做定期合并控制文件大小目标是把单个文件控制在128MB左右最少也要几十MB这套治理做完后NameNode的GC次数明显减少集群整体吞吐恢复了正常。小文件治理这件事越早做越省事等量变引起质变后再处理成本要高出非常多。3.4 NameNode安全模式与元数据守护安全模式是HDFS的自我保护状态。处于安全模式时文件系统是只读的数据块的复制和删除操作都会被暂停。常见的安全模式触发原因有两个一是NameNode启动过程中自动进入二是数据块上报比例未达到阈值。NameNode启动时进入安全模式不可怕它只是在等待DataNode上报数据块信息确认数据的副本情况。真正要警惕的是启动很久之后仍然停在安全模式不退出。这时候需要检查数据块上报的阈值设置以及是否有大量DataNode没上线。还有一个更严重的问题安全模式下有人会忍不住手动执行强制退出命令。这个命令会把集群置于一个看似正常实则异常的状态因为很多数据块的副本数是不足的后续的读写操作可能莫名其妙失败。如果业务压力不大我建议让NameNode在安全模式下把数据块复制工作先做完再自动退出而不是人为强制干预。元数据的守护还涉及fsimage和edits文件。fsimage是NameNode的元数据快照edits是增量日志。两者合并的操作叫checkpoint。如果合并异常或者edits文件过大NameNode启动时重放日志的时间会非常长。我遇到过一次极端情况某测试集群长时间没有做checkpointedits文件膨胀到几个GB大小NameNode重启后花了将近一个小时才完成日志重放。后来我把checkpoint的时间间隔调短同时定期手动触发一次合并再没出现过这种情况。4. YARN与作业运行问题4.1 资源调度器与队列配置导致的假死YARN把集群的计算资源抽象成Container资源调度器负责决定哪些应用可以拿到Container。默认的调度器是容量调度器它按队列划分资源。如果你的业务方提交的任务一直卡在ACCEPTED状态大多不是代码问题而是等不到调度器分配资源。这类问题排查时先看当前队列的资源使用情况我通常会直接从Web界面登录查看各个队列的运行和等待任务数。如果某个队列的资源已经被占满后面的作业就只能排队候补。容量调度器的配置核心是队列的资源占比。我踩过的一个典型坑是给一个队列配置了很高的资源上限又设置了很低的容许多任务数上限结果队列的资源用不满任务还在排队。资源上限和最大任务数之间需要匹配队列才能高效运转。另外容器调度和内存调度的比值设置必须合理。物理内存和虚拟内存的比值设置过高时可能出现一个Container物理内存已经快耗尽但虚拟内存还远超限制的情况导致作业被误杀。我的习惯是把比值设得宽松一些但要配合监控数据做判断不能一拍脑袋随便填。4.2 作业提交失败与Container启动异常作业提交失败的报错千奇百怪但归纳起来主要是几类class路径问题、依赖缺失问题、资源申请参数问题。其中class路径问题特别常见尤其在多个第三方jar包混在一起的时候。处理办法是把作业所需的依赖jar包统一放到一个目录通过参数显式指定避免依赖默认行为。Container启动异常方面我遇到最多的两种一是分配的虚拟内存超出限制被节点管理器杀掉二是磁盘本地目录空间不足导致shuffle数据写不进去。排查时先看节点管理器的日志它会明确写清楚杀掉Container的原因然后针对原因去调整参数。一个实用的经验在任务提交阶段先提交一个很小的测试作业比如只处理一个分区的简单任务确认整个链路是通的再提交正式的复杂作业。这样可以把环境问题和代码问题分隔开快速定位故障点。4.3 数据倾斜与MapReduce调优心得数据倾斜是MapReduce作业中最让人头痛的问题之一。直观表现就是大部分Map或Reduce任务早就跑完了只有那么一两个任务卡在那里迟迟不结束。根本原因是某个数据分区里集中了远超平均量的数据导致对应的任务处理时间被无限拉长。我处理过的一个真实案例某个统计作业按某个维度做聚合该维度下有一个特殊值的数量占了总量的八成结果这个Reduce任务跑了几个小时都结束不了。当时采用了预处理方案把这个特殊值单独提取出来走单独的聚合路径剩余正常数据再走常规聚合路径最后合并结果。任务时间从原来的几个小时降到了十几分钟。MapReduce调优方面有几组参数值得重点关注内存相关参数Map和Reduce的堆内存设置要和节点总内存匹配并行度相关参数Map和Reduce的并行任务数决定了作业的并发能力压缩相关配置中间结果和最终结果建议开启压缩能显著减少磁盘和网络IO调优没有银弹核心逻辑是让每一台机器的CPU、内存、磁盘、网络都均匀忙碌起来任何一项资源成为瓶颈整个作业的性能都会被它卡住。我习惯在调优过程中盯住资源监控面板观察各节点各项资源的使用曲线再针对瓶颈做定向优化。5. 集群性能优化与监控5.1 核心参数调优的关键取舍Hadoop的参数非常多网上的调优模板也五花八门。我的建议是不要盲目照搬模板而是理解每个参数影响的资源类型再结合自己集群的硬件配置做调整。内存参数是需要最先关注的。NameNode的堆内存决定了它能管理多少元数据DataNode的堆内存影响它的读写性能ResourceManager和NodeManager的堆内存则直接决定任务调度的能力。我给团队定过一个简单的估算方法物理内存的分配总和不应该超过节点总内存减去系统占用的部分否则操作系统自身的运行会受到影响出现频繁的内存交换整个集群的性能都会下降。IO参数方面重点是数据块大小和复制因子。数据块大小默认是128MB如果你的集群硬件条件好磁盘IO能力较强可以考虑调到256MB来减少文件分块数量。复制因子默认是3如果数据重要性不高可以酌情降到2以节约存储空间。但在这件事上我的建议偏保守生产环境不要轻易降低复制因子数据丢失的代价远比节省的存储成本高。5.2 监控指标与告警设置监控是运维人员的眼睛。没有监控的集群就像蒙着眼睛开车等出了事故才知道问题在哪儿。我在搭建监控体系时最关注几个核心维度NameNode的GC频率和耗时、DataNode的上线状态、节点磁盘使用率、集群网络IO吞吐、YARN队列资源使用率。告警设置的阈值不能一刀切。我见过一些团队把所有阈值都设得很激进结果告警风暴天天发生大家反而对告警麻木了。合理的做法是区分告警级别比如磁盘使用率超过80%发通知超过90%才发紧急告警NameNode的GC暂停超过某个阈值才告警避免每次小抖动都打扰值班人员。监控工具方面开源生态里有多种选择。如果你对部署成本敏感可以先从轻量的监控方案做起把节点的基础指标和关键服务状态采集到统一的展示界面。等集群规模大了再考虑更完善的监控体系。重点是先把数据采起来、存下来有历史数据才能做趋势分析。5.3 日常巡检清单定期巡检是防患于未然最好的方式。我整理过一份巡检清单每次巡检大概半小时覆盖了集群的关键健康指标检查各节点服务进程是否都在正常监听端口查看NameNode和ResourceManager的Web界面确认没有异常告警用命令检查HDFS整体容量和各目录的配额情况对比各节点磁盘使用率看是否需要启动数据均衡检查最近一段时间的GC日志观察内存使用趋势抽查最近提交的作业运行完成情况确认没有大面积失败巡检不在于做得有多频繁而在于记录和对比。每次巡检的数据都应该留存下来过一段时间再看很多问题在爆发之前其实都有迹可循。什么叫异常跟历史数据不一样才是异常。没有历史数据你连什么是正常都说不清楚。6. 实战排查记录与经验沉淀6.1 一个典型的全链路排查案例分享一个我印象很深的案例。某天下午业务侧反馈一个批量任务写HDFS的速度突然变得很慢任务处理时间比平时多了一倍。从监控面板上看集群的总写入吞吐明显下降但各节点CPU和内存使用率并不高看起来不像资源耗尽导致的。我第一反应是怀疑某个数据节点出了问题。登录NameNode查看数据块分布发现有几台DataNode的副本数量明显比别的节点少进一步排查发现其中一台DataNode上报的磁盘总容量比预期少了很多磁盘出现了坏道且被标记为坏盘。DataNode在写入时会优先选择健康且空间充足的磁盘当坏盘被排除后这台节点的有效容量骤减原本应该写入它的数据被迫写到了其他节点。随着这类数据块的复制和平衡发生整个集群的网络IO被放大整体写入速度随之下降。定位到根因后我们把该节点下线、替换磁盘、重新加入集群再触发一次数据均衡。业务写入速度在两小时后恢复了正常。这个案例给我最大的教训是HDFS的吞吐一旦异常首先要看是不是有节点在半死状态。它看起来活着实际却在拖累整个集群。6.2 高频错误对照速查表把常见报错和对应的排查方向整理成一张速查表会比翻文档高效很多报错现象常见根因快速处理方向DataNode无法连接NameNode网络不通或核心配置不一致检查端口连通性和core-site.xml的地址配置作业一直等待资源队列资源不足或配置上限偏小查看队列状态并调整资源比例容器或进程被节点管理器杀掉虚拟内存或物理内存超限调整内存比值参数并确认实际内存占用磁盘空间不足数据增长超预期或未及时清理扩容或清理过期数据完善容量告警安全模式无法退出数据块上报比例不足或副本缺失检查DataNode状态和副本情况元数据目录元数据异常多个服务共用目录或误操作从备份恢复或人工重建后重新配置这张表不可能覆盖所有问题但方向上值得参考。实际排查时一定要回到具体日志中去确认根因而不是拿着报错硬套。6.3 踩坑之后沉淀的核心体会这些年踩过的坑不少能写下来的都是交付过真金白银教训的。第一永远保留一份可用的备份。包括NameNode元数据的外置备份、配置文件的版本化保存、关键脚本的归档。Hadoop集群不会因为你做对了什么而奖励你但会因为你没做备份而惩罚你。一次元数据误删就能把整个集群打回原形这种风险完全可以通过备份来规避。第二变更前先想好回滚方案。不管是升级版本还是修改参数先确认改坏了能不能回到原来的状态。我每次做变更之前都会记下当前的配置快照和关键版本信息变更后保留一段时间确认稳定了才清理。这个习惯帮我躲过了好几次升级引发的重大故障。第三日志才是最终的裁判。很多人在Hadoop问题上走了弯路都是因为过早地猜测原因而不是去读日志。遇到任何问题第一步永远是把相关组件的日志打开从第一条异常开始顺着堆栈找下去绝大多数问题的根因都会浮出水面。第四也是我最想强调的一点Hadoop集群的问题往往不是某一个组件的问题而是整个系统协同关系上的问题。排查时要站在集群整体视角去观察而不是局限在某一个服务进程里。DataNode挂了看NameNode日志任务失败看资源管理器日志磁盘满了看数据均衡策略——把它们当成一个整体来看待很多困惑都会迎刃而解。最后说一句掏心窝的话Hadoop已经是一个非常成熟的技术栈网上资料浩如烟海但真正让你在故障面前不慌乱的永远是你自己亲手排查过的场景。这份小结里的每一条记录都对应着一段真实的排查经历。如果你也准备踏入这个领域或者正在跟集群故障搏斗希望这份问题小结能让你少走几步弯路、少熬几个大夜。