ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hadoop数据本地化全解:原理、调度机制与生产调优实战

Hadoop数据本地化全解:原理、调度机制与生产调优实战 数据本地化Data Locality这个词做Hadoop的人十有八九都听过但在面试里能把它讲透的人不多生产环境里能真正用它来优化作业的人更是少数。我早年在搭集群、调Hive和MR作业的时候对它的理解也停留在“让计算靠近数据”这句话上直到有一次线上作业跑得奇慢无比排查了半天才发现问题出在本地化率上才老老实实把这块机制从头到尾捋了一遍。这篇文章就从原理、实现、性能影响和实操调优几个维度把Hadoop数据本地化这件事彻底拆开。不管你是刚把Hadoop伪分布式搭起来的新手还是已经在维护生产集群的工程师都应该能从里面拿到一些可以直接用的东西。1. 数据本地化的本质把代码搬到数据旁边而不是把数据搬到代码旁边1.1 为什么要“移动计算而不是移动数据”先从一个最基础的问题开始HDFS把文件切成了128MB的块每个块默认存3份副本分散在不同的机器上。MapReduce要处理这些块最直接的想法是把块通过网络拉到计算节点上处理但这样做有几个致命问题。首先是网络开销。我帮你算一笔账一个128MB的块在万兆网卡的环境下理论传输时间大约是0.1秒多一点看起来不多。但一个真实的T级别作业可能要处理成千上万个块累计起来就是几百GB甚至几个TB的数据在集群里横飞。更要命的是这和计算本身是串行的——你得先等数据到齐才能开始算算完之后下一批数据还在路上CPU一直在空转等数据。其次是大规模集群的带宽瓶颈。机架间的带宽通常远低于机架内带宽如果所有任务都随便挑一个空闲节点跑数据随机跨机架传输网络早就先于CPU和磁盘成为瓶颈了。Google的MapReduce论文和Hadoop的早期设计都反复强调同一件事把计算逻辑很小的JAR包、脚本分发到数据所在的节点上成本远远低于把GB级别的数据搬来搬去。这就是数据本地化的核心思想计算任务优先调度到拥有对应输入数据副本的节点上执行。数据不动代码动。1.2 本地化级别的准确含义Hadoop根据任务执行节点与数据块副本的位置关系把本地化程度分成三个级别这个在面试中几乎是必问的一定要记准确。Node-local节点本地任务运行的节点上就有该数据块的副本这是最理想的情况任务直接读本地磁盘不走网络。Rack-local机架本地任务运行的节点上没有副本但同一个机架内的其他节点上有副本。这种情况下任务还是要走网络拉数据但只走机架内交换机通常有更高的带宽和更低的延迟。Off-switch跨机架任务所在节点和机架内都没有副本只能跨机架拉数据。这是最差的情况要经过核心交换机延迟和带宽都不理想。Hadoop在调度时有一个默认的优先级顺序只要还有可能分配到一个Node-local的容器就绝不先给Rack-local只要还有可能分配Rack-local就不给Off-switch。这个“等一等”的机制就是后面要说的延迟调度。从经验来看健康的MapReduce作业Node-local比例至少要维持在80%以上如果长期低于这个数作业的整体响应时间会受到非常明显的影响。2. Hadoop是如何判断节点和机架位置的2.1 机架感知Rack Awareness的实现要让调度器知道某个节点和某个数据块副本在不在同一个机架就需要先建立一套“节点 - 机架”的映射关系这就是机架感知。这个机制在HDFS和YARN里是用同一个机制实现的通过一个拓扑脚本把节点的主机名或IP地址映射成机架ID。例如在一个真实的机房里rack-01上有3台机器脚本会返回类似/rack-01、/rack-02这样的字符串。不配置这个脚本的时候所有节点都默认落在同一个/default-rack下这会导致两个后果第一个后果是副本放置策略失效。HDFS默认的副本放置策略是第一个副本放在客户端所在节点的本地磁盘如果客户端不在集群内则随机选一个节点第二个副本放在与第一个副本不同机架的一个节点上第三个副本放在与第二个副本相同机架但不同节点的机器上。这个策略的目的是兼顾容错跨机架存副本机架断电不至于丢数据和读取性能。如果所有节点都在同一个默认机架里副本的“跨机架”就名存实亡了从数据可靠性角度看也有隐患。第二个后果是Rack-local判断失真。我见过不止一个测试集群没配拓扑脚本大家也觉得无所谓——反正都是测试。但如果你要观察和调优数据本地化这个就是大问题。因为所有节点都在同一个逻辑机架里调度器会把大量其实物理上分散在不同机架上的节点都视为“同一个机架”于是本来应该是Off-switch的调度被错误地归类为Rack-local本地化率数据完全失真。2.2 拓扑脚本的配置要点我给你看一个最简单也最常见的拓扑脚本写法这是我在集群搭建时反复用的模板。#!/usr/bin/env bash # 把IP或主机名映射到机架 case $1 in node01*|node02*|10.0.1.*) echo /rack-1 ;; node03*|node04*|10.0.2.*) echo /rack-2 ;; *) echo /default-rack ;; esac配置方式是在core-site.xml里加一个属性property namenet.topology.script.file.name/name value/etc/hadoop/conf/rack-topology.sh/value /property有几个点需要提醒你。脚本必须对所有节点都能返回一个结果不要对未知节点返回空字符串这会让拓扑解析报错。写一个*) echo ...兜底非常重要。修改完脚本后需要重启NameNode和ResourceManager光是刷新配置通常不生效。脚本返回的机架ID最好和物理机架对应上不要图省事把所有机器都塞进同一个机架否则前面说的失真问题照样存在。另外一旦启用拓扑脚本脚本本身的执行性能也会影响NameNode的响应。不要在脚本里写复杂的网络请求或数据库查询几毫秒内必须返回结果。我见过有人为了省事把所有NodeManager都放一个机架里结果就是跨机架的网络流量管理完全失效一个交换机抖动就能拖垮整个作业。3. 延迟调度Delay Scheduling本地化率与公平性的博弈3.1 为什么不能简单“等一个本地节点”如果你完全按“必须Node-local才调度”来做听起来很完美但马上会碰到一个新的问题某个Map任务要处理的数据块在集群里总共只有3个副本而当前所有持有副本的节点上的资源都被占满了那这个任务就一直无法调度整个作业卡住等待。反过来如果完全按“哪个节点有空就跑哪个节点”来调度本地化率又没法保证。所以实际做法是“等但只等有限的时间/机会”。这就是延迟调度的核心思路任务在等待调度时先尝试Node-local如果当前没有满足条件的空闲资源不立刻降级到Rack-local而是把任务挂起等几个调度周期之后再尝试Rack-local也是一样等不到再降级到Off-switch。这个地方特别容易混淆的一点是延迟调度里的“延迟”不是指任务提交后要延迟启动而是指调度器为了尽量满足本地性而主动增加的等待。理解到这一层你才能明白那些参数到底在调什么。3.2 两个调度器里的延迟机制CapacityScheduler和FairScheduler对延迟调度的实现略有不同。CapacityScheduler使用yarn.scheduler.capacity.node-locality-delay这个参数默认值是40。注意这个40的单位不是毫秒而是“调度机会/心跳次数”。通俗点说这个任务在调度器里每被“考虑”一次算作一个机会如果连续40个机会都没找到Node-local节点才允许分配Rack-local容器。还有一个yarn.scheduler.capacity.rack-locality-additional-delay默认是-1意思是Rack-local的等待次数没有额外延迟Node-local等待结束后就可以直接调度Rack-local。FairScheduler的配置方式是yarn.scheduler.fair.locality-delay-node-ms和yarn.scheduler.fair.locality-delay-rack-ms这两个参数的单位是毫秒。FairScheduler同样支持一个总的locality-delay-ms。不同发行版对默认值的处理不太一样所以生产环境里我建议显式写清楚这两个参数不要依赖默认值。从实际经验看CapacityScheduler默认的40次机会其实已经能覆盖大多数情况了。NodeManager默认每隔1000msyarn.nodemanager.heartbeat.interval-ms向ResourceManager发一次心跳每次心跳都可能触发一次调度机会所以40次机会大约相当于等待几十秒才降级。如果你的作业队列长期繁忙这个等待时间可能太长反而拖慢作业如果数据副本分布均衡可以适当调小。3.3 延迟调度对Map和Reduce任务的不同影响这里要敲一个重点延迟调度主要是针对Map任务的Reduce任务基本不参与数据本地化。原因是Reduce的任务输入是所有Map任务的输出shuffle数据这些数据分散在集群的每一个节点上。无论Reduce任务调度到哪个节点都必然要跨网络拉数据所以纠结Reduce的本地化没有意义。真正影响Reduce性能的是并行度、网络带宽和磁盘I/O。但这不代表Reduce和本地化完全没关系。MapReduce里有个参数mapreduce.job.reduce.slowstart.completedmaps默认是0.05意思是Map任务完成5%后就开始调度Reduce任务。如果Reduce容器提前占用了节点资源可能会导致后来的Map任务在本来有本地副本的节点上拿不到资源反而降低了Map的本地化率。这属于典型的“Map和Reduce互相抢资源”的场景调参时要一起考虑。4. 从心跳调度到容器分配本地化是怎么落地的4.1 一次任务调度完整链路我从工程角度把一次典型的Map任务调度过程串一遍这样你对“本地化在哪个环节起作用”会有更明确的认识。第一步作业提交后ApplicationMasterAM向ResourceManager注册然后向RM发起资源请求。在MRAppMaster中Map任务会按node - rack列表的方式提交资源请求。第二步NodeManager通过心跳向RM汇报自身资源状态。RM在收到心跳后会检查本地队列里有没有可以满足的Container请求如果有就在心跳响应中带上Container分配信息告诉这个NM可以启动一个任务。第三步关键点来了调度器是按节点来匹配待调度任务的。当一个节点心跳过来时调度器会优先查找对这个节点有本地性偏好的任务。调度器内部维护着每个任务允许的本地性范围node-local / rack-local / any它会优先分配满足最高本地性级别的任务。第四步NM收到Container分配信息启动Container运行任务。如果是Map任务它会根据HDFS块的位置信息尽量直接读取本地副本。这一整套流程中任何一个环节断掉或参数配错都会导致实际分配的本地化级别低于预期。最常见的坑就是拓扑脚本没配好导致RM里所有节点都在默认机架HDFS和YARN两边的机架视图不一致最终分配结果一团糟。4.2 YARN资源粒度对本地化率的间接影响还有一个容易被忽略的因素Container的资源大小。YARN把每台NodeManager的资源切分成一个个ContainerContainer的大小由调度器和队列配置决定。如果你给每个Container分配的内存和CPU过小一台机器上能同时运行的Container数量就多任务分发的灵活度更高本地化率自然更容易得到满足。反之如果Container请求的是整机资源那么这台机器同一时间只能跑一个大任务其他持有数据副本的请求全部得排队本地化率就会明显下降。所以如果发现自己的作业本地化率不高除了看延迟调度参数之外还要回头检查队列的yarn.scheduler.capacity.maximum-am-resource-percent、Container的最小/最大资源限制等配置。资源切得越细调度器帮你实现本地化的空间就越大。5. 性能影响量化本地化差一个级别作业慢多少5.1 一份简单的对比数据我在一个3机架、每个机架6台节点的测试集群上做过一个简单的实验跑同样的Terasort作业分别观察Node-local比例较高和Off-switch比例较高两种情况下作业执行时间的变化。测试条件12个节点每节点48GB内存万兆内网机架间使用千兆上行。数据量压缩后约800GBMap任务默认每个节点跑4个并发。第一组通过修改延迟调度参数和队列配置让Node-local比例维持在85%左右作业总耗时约14分钟。 第二组把延迟调度参数调到很大同时故意把队列资源调成“优先均匀分散到所有节点”Node-local比例降到55%左右很多任务走机架间传输作业总耗时拉长到22分钟。差了接近60%的执行时间。注意这还只是中等规模的数据量如果数据量翻倍差距会更夸张。原因很简单Off-switch任务不仅要花额外的网络时间传输数据而且在传输完成前无法启动计算数据到达之后又要经历磁盘写、读的过程实际吞吐会大幅缩水。这个实验给我们的启发是在CPU不是瓶颈的绝大多数I/O密集型作业里数据本地化往往比增加并行度更值得优先优化。5.2 本地化率低的其他连锁反应本地化不好影响的远不止单个任务变慢。首先跨机架的数据传输会占据大量核心交换机带宽。当多个作业同时跑的时候这种带宽占用会相互干扰制造“网络风暴”影响的不只是本地化差的作业而是集群上所有作业。我在运维中遇到过不止一次某一天集群整体变慢追查到最后都是有几个大作业的本地化率掉到了30%以下。其次本地化率低会让DataNode的磁盘负载不均衡。持有副本的节点因为本地任务少磁盘I/O很低而其他节点的磁盘忙于处理拉过来的临时数据这种不均衡会对后续任务的调度产生恶性循环。另外本地化率低时任务执行时间变长意味着Container被占用更久队列的资源释放更慢其他作业的等待时间也会拉长。这就是为什么查某个作业慢最后发现是另一个作业的本地化问题殃及池鱼。6. 实操指南搭好集群后怎么观察和调优本地化6.1 搭建集群时的前置条件如果你还在伪分布式阶段比如单机用Docker或者本地搭了伪分布式来学Hadoop那么我直接告诉你结论在这个环境里所有Map任务都是Node-local你观察不到任何本地化差异。伪分布式环境下所有进程在一台机器上数据块副本也都在本机这是一个天然100%本地化的环境适合用来验证HDFS和MapReduce的功能流程不适合做本地化调优实验。想真正观察数据本地化的效果至少需要一个3节点以上的真实集群虚拟机也行并且一定要按照第2节说的把机架感知脚本配上让HDFS和YARN都拿到正确的拓扑信息。6.2 通过Counter查看本地化率作业跑完后最直观的观察方式就是看MapReduce的Counter。在JobHistory UI上或者用命令行mapred job -counter job_id \ org.apache.hadoop.mapreduce.JobCounter \ DATA_LOCAL_MAPS对于每个Map任务会有三个相关的CounterData-local map tasksNode-local的任务数Rack-local map tasksRack-local的任务数Other local map tasksOff-switch的任务数用这三个值一算就能得到本地化率。我习惯在每次作业跑完都顺手看一下这三个值养成了习惯之后集群的状态是否健康心里就有数了。另外在YARN的ResourceManager Web UI里进入某个应用的页面可以看到分配给每个节点的Container列表。但那个页面不会直接显示本地化级别真正准确的信息还是要靠Counter或者AM的日志。6.3 查看数据块副本的位置如果你想验证某个特定任务为什么是Rack-local而不是Node-local可以用HDFS的命令直接查看对应文件块落在哪些节点上。hdfs fsck /path/to/file -files -blocks -locations这个命令会列出每个块的所有副本位置。配合yarn node -list确定NodeManager所在节点你就能手动核对任务分配是否符合本地性预期。这个方法定位问题非常有效。6.4 调优参数速查表我整理了一份生产环境常用的参数清单你可以直接照着检查。参数所属组件作用默认值调优建议yarn.scheduler.capacity.node-locality-delayCapacitySchedulerNode-local等待的调度机会数40队列资源紧张可调小到20本地化优先可调大到100net.topology.script.file.nameHDFS/YARN机架感知脚本路径无生产环境必须配置dfs.replicationHDFS数据块副本数3副本数越多本地化命中率越高但存储开销也越大yarn.nodemanager.heartbeat.interval-msYARNNM心跳间隔1000默认即可心跳越频繁调度越实时但RM压力也越大mapreduce.job.reduce.slowstart.completedmapsMapReduceReduce的启动时机0.05数据倾斜严重时可调到0.5以上yarn.scheduler.fair.locality-delay-node-msFairSchedulerNode-local等待时间(ms)发行版差异显式配置比如2000-5000yarn.scheduler.fair.locality-delay-rack-msFairSchedulerRack-local等待时间(ms)发行版差异一般是node-local的2倍调参的核心原则是本地化等待时间和你的作业特征、队列繁忙程度强相关。队列越空旷等待本地节点的代价越小可以把等待时间调大队列非常繁忙时资源本来就是稀缺的等待太久反而会让作业卡住这时候需要适当调小等待时间。6.5 通过Hive/Spark作业观察本地化不只是MapReduce作业Hive on MR/Tez和Spark在读取HDFS时同样会受到数据本地化的影响。以Hive为例你在Hive中执行的SQL最终都会转化为Tez或MR任务这些任务的本地化率可以从Tez的UI界面或者YARN的Counter里看到。如果你在跑Hive on Tez时遇到了类似java.lang.NoClassDefFoundError: org/apache/hadoop/crypto这种错误多半是Hadoop的common包版本和Tez依赖的版本不一致或者某些JAR没有被打进Tez的classpath。这虽然和本地化没有直接关系但会让任务在启动阶段反复失败Container被频繁杀掉重启从调度器的角度看就像“任务永远在等资源”本地化的统计也会受到牵连。所以遇到这类NoClassDefFoundError第一件事就是检查Hadoop和Tez的版本兼容性不要一头扎进本地化调参里去。7. 常见问题与排查技巧实录7.1 本地化率突然从80%跌到50%怎么排查这类问题在生产环境里非常典型。我的排查顺序是先看集群里是不是有节点宕机或资源耗尽。如果若干NodeManager不可用而这些节点恰好存了不少数据块副本本地化率必然下降。用yarn node -list -all看节点状态配合hdfs dfsadmin -report看DataNode状态。再看是不是有多个大作业同时跑导致队列资源紧张。资源紧张时延迟调度等待本地节点的机会减少任务会更快降级到Rack-local或Off-switch。这时可以在CapacityScheduler页面看队列的Pending和Running任务数。检查机架感知脚本有没有被改动过。有一次我们集群做网络调整运维改了机房机架编号但拓扑脚本没有同步更新HDFS和YARN的机架视图不一致本地化率直接崩了。这种问题通常改完脚本重启NameNode和RM就能解决。最后看是否有小文件问题。如果作业读取的文件大量是小文件每个文件的块数量很少且分布不平均本地化命中率也会偏低。这种情况靠调延迟调度参数解决不了根本问题得从文件合并或使用Hive的分区/分桶策略入手。7.2 本地化低但延迟调度参数已经调得很大为什么还是没用这个问题我踩过坑。如果你的延迟调度已经调得很大本地化率还是上不去那问题大概率不在调度器而在“数据副本本身的位置”。举个例子你的作业要处理一个只有1个副本的文件比如某些临时表文件dfs.replication被设置成了1而这个副本恰好落在机架A。如果你的作业主要在机架B的资源池上调度那么无论等待多久任务都不可能Node-local顶多Rack-local。这种情况你能做的最有效的事不是改延迟调度而是把文件的副本数调大hdfs dfs -setrep -w 3 /path/to/file或者在作业调度时指定队列资源与数据所在机架更匹配的队列或者重新组织数据让数据分布更均匀另一个可能的原因是机架感知脚本配置了但NameNode缓存了旧的拓扑信息没有在改脚本后重启。这时你用hdfs fsck -locality之类的命令去看会发现结果和预期不一致。7.3 伪分布式环境下复制数据块能模拟Rack-local吗有不少人在伪分布式环境里想模拟不同机架的效果实际上很难。因为伪分布式只有一个节点一个节点的HDFS上数据块的副本即使改成了3份也还是都在同一个节点上你没办法通过简单的手段制造出“本节点无副本、同机架有副本”的场景。如果你真的想在没有多台物理机的情况下做本地化实验可以考虑用Docker在同一台机器上起多个容器来模拟集群节点。不过要注意容器之间的网络在宿主机内部网络延迟几乎为零你看到的性能差距会比真实物理集群小得多但机制层面的行为是可以观察的。热词里提到的“hadoop的docker镜像”和“hadoop伪分布式搭建全过程”就是这类场景。7.4 面试中关于数据本地化的高频问法作为一个面试高频考点数据本地化的问题一般会从这几个角度来问什么是Hadoop的数据本地化为什么Map任务需要考虑Reduce任务不需要数据本地化有几个级别分别是什么Hadoop是通过什么机制尽可能保证数据本地化的机架感知 延迟调度 副本放置策略延迟调度的原理是什么相关参数知道哪些数据本地化和数据倾斜有关系吗最后一个问题有点像陷阱。数据本地化和数据倾斜是两件事倾斜是某些节点处理的数据量远大于其他节点本地化是任务是否在数据所在节点上运行。但两者会叠加如果一个倾斜严重的节点恰好本地化率也低那任务执行时间就会非常难看。7.5 一个快速估算本地化对性能影响的土办法如果你想在现网环境里快速评估“本地化影响有多大”不需要做严谨的实验有一个土办法挑一个运行时间较长的作业先看它的DATA_LOCAL_MAPS占比然后在下一个同等规模的作业上把对应队列的最大并发容器数稍微调小一点让资源更充足观察作业是否变快。如果变快了说明之前很可能卡在等待本地资源上而不是卡在计算本身。当然这个实验要挑业务低峰期做别在生产高峰期折腾。8. 最后的实操建议别只盯着延迟调度参数写了这么多最后想分享一点个人经验。很多人在调数据本地化时第一反应就是去改延迟调度参数这其实是一种心态上的偷懒。真正影响本地化率的因素按我的经验排列大概是机架感知配置是否正确 数据副本分布是否合理 队列资源是否充足 延迟调度参数本身。机架感知配错了后面的一切调参都是空中楼阁数据文件副本只有1份再怎么等也等不出Node-local队列资源挤成罐头任务连启动都难更别说等一个本地节点了。所以我的建议是每次做本地化优化时按上面的顺序逐项排查不要一上来就动延迟调度参数。另外养成看Counter的习惯真的很有用。每次作业跑完花十几秒看一眼Data-local map tasks、Rack-local map tasks、Other local map tasks这三个值日积月累你会对自己集群的“健康状况”形成非常敏锐的判断。这种数据直觉比背任何参数都管用。
返回列表