
1. Hadoop计算引擎的演进背景2006年当Doug Cutting将Hadoop从Nutch项目中分离出来时他可能没想到这个基于Google论文实现的分布式系统会成为大数据时代的基石。早期的Hadoop 1.x版本采用了一种现在看来颇具古典色彩的设计——Master/Slave架构下的JobTracker与TaskTracker组合。这套机制在Web 2.0数据爆炸的年代支撑了包括Yahoo!、Facebook等互联网巨头的海量数据处理需求。JobTracker作为集群的大脑承担着双重职责既要管理所有作业的生命周期从提交到完成又要监控整个集群的资源状况。这种设计在集群规模较小时表现尚可但当节点数量突破4000台时如Yahoo!在2010年公开的案例单点瓶颈问题开始凸显。一个典型的症状是随着作业数量增加JobTracker的RPC队列会出现积压导致整个集群响应延迟飙升。TaskTracker作为执行单元采用被动领取任务的工作模式。每个节点上的TaskTracker会通过心跳机制默认3秒一次向JobTracker汇报本机状态并领取新的任务指令。这种设计虽然简单可靠但存在明显的资源浪费——节点即使空闲也必须等待心跳周期才能获取新任务。在早期的淘宝技术团队分享中就曾提到过由于心跳间隔导致的集群资源利用率长期低于60%的问题。2. JobTracker的架构解剖2.1 核心组件交互模型JobTracker的内部构造远比表面看起来复杂。当用户提交一个MapReduce作业时JobTracker会启动一个称为JobInProgress的对象来跟踪作业状态。这个对象内部维护着TaskInProgress列表记录每个map/reduce任务作业计数器统计进度任务调度队列基于优先级或FIFO资源管理模块采用了一种称为槽位slot的抽象概念。每个TaskTracker会汇报自己的map slot和reduce slot数量JobTracker根据这些信息进行任务分配。这种设计后来被证明是导致资源碎片化的根源——一个节点可能有空闲的map slot但作业需要reduce slot此时资源就无法被有效利用。2.2 容错机制实现细节面对节点故障JobTracker有一套完整的恢复策略心跳超时检测默认10分钟将故障节点上的任务重新加入调度队列对已经运行超过一定比例的任务采用推测执行Speculative Execution在京东2013年的案例中他们发现当集群规模达到2000节点时仅心跳检测就会消耗JobTracker 15%的CPU资源。更棘手的是如果JobTracker本身发生故障整个集群将完全不可用——这也是后来YARN将资源管理和作业调度分离的主要原因。3. TaskTracker的运行机制3.1 任务执行流程每个TaskTracker启动后会创建固定数量的map/reduce槽位通过mapred.tasktracker.map.tasks.maximum参数配置。当收到JobTracker的任务分配指令后从HDFS下载任务所需的jar包和输入分片启动独立的JVM运行任务通过mapred.child.java.opts配置内存通过umbilical接口向父TaskTracker汇报进度任务完成后上传结果到HDFS在早期的百度日志处理系统中工程师们发现TaskTracker的JVM启动开销有时会占到任务总时间的20%。这促使社区后来引入了JVM重用机制通过mapred.job.reuse.jvm.num.tasks配置。3.2 资源隔离的局限性TaskTracker对CPU资源的隔离几乎为零仅通过Linux原生进程调度进行管理。内存方面虽然可以通过mapred.child.java.opts限制任务内存但缺乏强制约束。某知名电商平台曾发生过由于某个map任务内存泄漏导致整个节点被交换分区拖慢的案例。磁盘和网络I/O更是完全没有隔离这在大规模集群中经常引发邻居问题noisy neighbor——一个高I/O任务会影响同节点其他任务的性能。直到后来才出现通过cgroups进行资源隔离的补丁但这从未成为主流功能。4. 经典MapReduce作业生命周期4.1 作业提交阶段当用户运行hadoop jar命令时RunJar进程会将作业配置打包成job.xml将jar包和配置文件上传到HDFS默认在/tmp/hadoop-$USER/mapred/staging通过RPC调用JobTracker.submitJob()方法在早期的Hadoop版本中这个阶段有个隐蔽的陷阱——如果客户端与HDFS集群的块大小配置不一致可能导致输入分片计算错误。某金融公司就曾因此得到错误的统计结果直到他们统一了所有节点的hdfs-site.xml配置。4.2 任务调度阶段JobTracker的调度器默认是JobQueueTaskScheduler会检查TaskTracker的心跳报告根据空闲槽位和作业优先级选择任务考虑数据本地性data locality原则数据本地性分为三个级别NODE_LOCAL数据就在本节点RACK_LOCAL数据在同机架其他节点OFF_RACK数据在其他机架在电信行业的一个真实案例中通过调整mapred.reduce.parallel.copies参数控制reduce阶段数据拷贝的并行度某省级运营商将夜间批处理作业时间缩短了37%。4.3 Shuffle与Sort阶段这是MapReduce最精妙也最容易出问题的阶段Map任务将输出写入环形缓冲区mapreduce.task.io.sort.mb控制大小达到阈值后触发spill到磁盘期间会进行分区partition和排序sortReduce任务通过HTTP拉取属于自己的分区数据某视频网站曾报告当单个reduce任务处理数据超过8GB时shuffle阶段经常失败。最终他们通过调整mapred.reduce.slowstart.completed.maps控制reduce任务启动时机和mapred.job.reduce.input.buffer.percent控制reduce阶段内存使用比例解决了这个问题。5. 性能调优实战技巧5.1 配置参数黄金组合经过多年实践这些参数组合被证明对大多数场景有效property namemapreduce.task.io.sort.mb/name value256/value !-- 排序缓冲区大小 -- /property property namemapreduce.map.sort.spill.percent/name value0.80/value !-- 溢出阈值 -- /property property namemapreduce.reduce.shuffle.parallelcopies/name value20/value !-- reduce并行拷贝数 -- /property某零售企业的测试表明将这些参数与合理的combiner配合使用能使TeraSort作业性能提升40%以上。5.2 常见故障排查指南症状作业卡在map 100% reduce 0%状态检查reduce任务日志中的Connection refused错误调整mapred.reduce.parallel.copies降低网络负载症状TaskTracker频繁被JobTracker列入黑名单检查节点硬件时钟同步NTP配置监控磁盘健康状态bad sector会导致任务超时在2012年某次Hadoop峰会上LinkedIn工程师分享了一个经典案例由于交换机固件bug导致机架内网络偶尔丢包造成大量任务超时。他们最终通过分析TaskTracker的syslog发现了规律性的网络中断记录。6. 向YARN的演进之路6.1 架构缺陷的必然变革JobTracker的设计硬伤在2010年后变得越来越明显扩展性瓶颈单个JobTracker最多支持约4000节点资源利用率低静态槽位分配导致资源碎片不支持新计算模型如迭代计算、流处理等这促使了YARNYet Another Resource Negotiator的诞生。在YARN中ResourceManager负责纯资源管理ApplicationMaster负责作业生命周期NodeManager取代TaskTracker某跨国公司的测试数据显示同样的硬件集群从Hadoop 1.x迁移到YARN后资源利用率从58%提升到了82%。6.2 兼容性过渡方案对于仍需运行传统MapReduce作业的用户YARN提供了MRv2兼容模式部署mapreduce-legacy模块使用特殊的ApplicationMaster实现通过配置映射将旧参数转换为YARN等效参数但在实际迁移中工程师们发现某些依赖JobTracker API的监控工具需要重写。某互联网公司开发了一个透明的API兼容层这使他们能够在6个月内完成2000个作业的平滑迁移。