Hadoop调优

1、Job执行三原则

  • 充分利用集群资源
  • reduce阶段尽量放在一轮
  • 每个task的执行时间要合理

1.1、原则一 充分利用集群资源

        Job运行时,尽量让所有的节点都有任务处理,这样能尽量保证集群资源被充分利用,任务的并发度达到最大。可以通过调整处理的数据量大小,以及调整map和reduce个数来实现。

  • Reduce个数的控制使用“mapreduce.job.reduces”
  • Map个数取决于使用了哪种InputFormat,默认的TextFileInputFormat将根据block的个数来分配map数(一个block一个map)。

1.2、原则二 ReduceTask并发调整

努力避免出现以下场景

  • 观察Job如果大多数ReduceTask在第一轮运行完后,剩下很少甚至一个ReduceTask刚开始运行。这种情况下,这个ReduceTask的执行时间将决定了该job的运行时间。可以考虑将reduce个数减少。
  • 观察Job的执行情况如果是MapTask运行完成后,只有个别节点有ReduceTask在运行。这时候集群资源没有得到充分利用,需要增加Reduce的并行度以便每个节点都有任务处理。

1.3、原则三 Task执行时间要合理

        一个job中,每个MapTask或ReduceTask的执行时间只有几秒钟,这就意味着这个job的大部分时间都消耗在task的调度和进程启停上了,因此可以考虑增加每个task处理的数据大小。建议一个task处理时间为1分钟。

2、Shuffle调优

        Shuffle阶段是MapReduce性能的关键部分,包括了从MapTaskask将中间数据写到磁盘一直到ReduceTask拷贝数据并最终放到Reduce函数的全部过程。这一块Hadoop提供了大量的调优参数。

2.1、Map阶段 

(1)判断Map内存使用

        判断Map分配的内存是否够用,可以查看运行完成的job的Counters中(历史服务器),对应的task是否发生过多次GC,以及GC时间占总task运行时间之比。通常,GC时间不应超过task运行时间的10%,即GC time elapsed (ms)/CPU time spent (ms)<10%。

Map需要的内存还需要随着环形缓冲区的调大而对应调整。可以通过如下参数进行调整。 

mapreduce.map.memory.mb

Ma需要的CPU核数可以通过如下参数调整

mapreduce.map.cpu.vcores

可以看到内存默认是1G,CPU默认是1核。 

如果集群资源充足建议调整:

mapreduce.map.memory.mb=3G(默认1G)mapreduce.map.cpu.vcores=1(默认也是1)

环形缓冲区:

        Map方法执行后首先把数据写入环形缓冲区,为什么MR框架选择先写内存而不是直接写磁盘?这样的目的主要是为了减少磁盘i/o

  • 环形缓冲默认100M(mapreduce.task.io.sort.mb),当到达80%(mapreduce.map.sort.spill.percent)时就会溢写磁盘。
  • 每达到80%都会重写溢写到一个新的文件。

        当集群内存资源充足,考虑增大mapreduce.task.io.sort.mb提高溢写的效率,而且会减少中间结果的文件数量。

建议:

  • 调整mapreduce.task.io.sort.mb=512M。
  • 当文件溢写完后,会对这些文件进行合并,默认每次合并10(mapreduce.task.io.sort.factor)个溢写的文件,建议调整mapreduce.task.io.sort.factor=64。这样可以提高合并的并行度,减少合并的次数,降低对磁盘操作的次数。

(2)Combiner

        在Map阶段,有一个可选过程,将同一个key值的中间结果合并,叫做Combiner。(一般将reduce类设置为combiner即可)通过Combine,一般情况下可以显著减少Map输出的中间结果,从而减少shuffle过程的网络带宽占用。

建议:不影响最终结果的情况下,加上Combiner!!

2.2、Copy阶段

  • 对Map的中间结果进行压缩,当数据量大时,会显著减少网络传输的数据量,
  • 但是也因为多了压缩和解压,带来了更多的CPU消耗。因此需要做好权衡。当任务属于网络瓶颈类型时,压缩Map中间结果效果明显。
  • 在实际经验中Hadoop的运行的瓶颈一般都是IO而不是CPU,压缩一般可以10倍的减少IO操作

2.3、Reduce阶段

(1)Reduce资源

每个Reduce资源

mapreduce.reduce.memory.mb=5G(默认1G)
mapreduce.reduce.cpu.vcores=1(默认为1)

(2)Copy

        ReduceTask在copy的过程中默认使用5(mapreduce.reduce.shuffle.parallelcopies参数控制)个并行度进行复制数据。

该值在实际服务器上比较小,建议调整为50-100.

(3)溢写归并

        Copy过来的数据会先放入内存缓冲区中,然后当使用内存达到一定量的时候spill磁盘。这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置。这个内存大小的控制是通过mapreduce.reduce.shuffle.input.buffer.percent(default 0.7)控制的。

        shuffile在reduce内存中的数据最多使用内存量为:0.7 × maxHeap of reduce task,内存到磁盘merge的启动可以通过mapreduce.reduce.shuffle.merge.percent(default0.66)配置。

        copy完成后,reduce进入归并排序阶段,合并因子默认为10(mapreduce.task.io.sort.factor参数控制),如果map输出很多,则需要合并很多趟,所以可以提高此参数来减少合并次数。

mapreduce.reduce.shuffle.parallelcopies #复制数据的并行度,默认5;建议调整为50-100
mapreduce.task.io.sort.factor #一次合并文件个数,默认10,建议调整为64
mapreduce.reduce.shuffle.input.buffer.percent #在shuffle的复制阶段,分配给Reduce输出缓冲区占堆内存的百分比,默认0.7
mapreduce.reduce.shuffle.merge.percent #Reduce输出缓冲区的阈值,用于启动合并输出和磁盘溢写的过程

3、Job调优

3.1、推测执行

        集群规模很大时(几百上千台节点的集群),个别机器出现软硬件故障的概率就变大了,并且会因此延长整个任务的执行时间推测执行通过将一个task分给多台机器跑,取先运行完的那个,会很好的解决这个问题。对于小集群,可以将这个功能关闭。

建议: 

  • 大型集群建议开启,小集群建议关闭!
  • 集群的推测执行都是关闭的。在需要推测执行的作业执行的时候开启

3.2、Slow Start

        MapReduce的AM在申请资源的时候,会一次性申请所有的Map资源,延后申请reduce的资源,这样就能达到先执行完大部分Map再执行Reduce的目的。

mapreduce.job.reduce.slowstart.completedmaps

当多少占比的Map执行完后开始执行Reduce。默认5%的Map跑完后开始起Reduce。
如果想要Map完全结束后执行Reduce调整该值为1

3.3、小文件优化

  • HDFS:hadoop的存储每个文件都会在NameNode上记录元数据,如果同样大小的文件,文件很小的话,就会产生很多文件,造成NameNode的压力。
  • MR:Mapreduce中一个map默认处理一个分片或者一个小文件,如果map的启动时间都比数据处理的时间还要长,那么就会造成性能低,而且在map端溢写磁盘的时候每一个map最终会产生reduce数量个数的中间结果,如果map数量特别多,就会造成临时文件很多,而且在reduce拉取数据的时候增加磁盘的IO。

如何处理小文件?

  • 从源头解决,尽量在HDFS上不存储小文件,也就是数据上传HDFS的时候就合并小文件
  • 通过运行MR程序合并HDFS上已经存在的小文件
  • MR计算的时候可以使用CombineTextInputFormat来降低MapTask并行度

3.4、数据倾斜

MR是一个并行处理的任务,整个Job花费的时间是作业中所有Task最慢的那个了。

为什么会这样呢?为什么会有的Task快有的Task慢?

  • 数据倾斜,每个Reduce处理的数据量不是同一个级别的,所有数据量少的Task已经跑完了,数据量大的Task则需要更多时间。
  • 有可能就是某些作业所在的NodeManager有问题或者container有问题,导致作业执行缓慢。

数据倾斜:

那么为什么会产生数据倾斜呢?
数据本身就不平衡,所以在默认的hashpartition时造成分区数据不一致问题

那如何解决数据倾斜的问题呢?

  • 默认的是hash算法进行分区,我们可以尝试自定义分区,修改分区实现逻辑,结合业务特点,使得每个分区数据基本平衡
  • 可以尝试修改分区的键,让其符合hash分区,并且使得最后的分区平衡,比如在key前加随机数n-key。
  • 抽取导致倾斜的key对应的数据单独处理。

如果不是数据倾斜带来的问题,而是节点服务有问题造成某些map和reduce执行缓慢呢?

        使用推测执行找个其他的节点重启一样的任务竞争,谁快谁为准。推测执行时以空间换时间的优化。会带来集群资源的浪费,会给集群增加压力。

4、YARN调优

4.1、NM配置

可用内存:

        刨除分配给操作系统、其他服务的内存外,剩余的资源应尽量分配给YARN。默认情况下,Map或Reduce container会使用1个虚拟CPU内核和1024MB内存,ApplicationMaster使用1536MB内存。

yarn.nodemanager.resource.memory-mb 默认是8192

CPU虚拟核数:

        建议将此配置设定在逻辑核数的1.5~2倍之间。如果CPU的计算能力要求不高,可以配置为2倍的逻辑CPU。

yarn.nodemanager.resource.cpu-vcores
# 该节点上YARN可使用的虚拟CPU个数,默认是8。目前推荐将该值设值为逻辑CPU核数的1.5~2倍之间

4.2、Container启动模式:

        YARN的NodeManager提供2种Container的启动模式。

        默认,YARN为每一个Container启动一个JVM,JVM进程间不能实现资源共享,导致资源本地化的时间开销较大。针对启动时间较长的问题,新增了基于线程资源本地化启动模式,能够有效提升container启动效率。

yarn.nodemanager.container-executor.class
  • 设置为“org.apache.hadoop.yarn.server.nodemanager.DefaultContainerExecutor”,则每次启动container将会启动一个线程来实现资源本地化。该模式下,启动时间较短,但无法做到资源(CPU、内存)隔离。
  • 设置为“org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor” ,则每次启动container都会启动一个JVM进程来实现资源本地化。该模式下,启动时间较长,但可以提供较好的资源(CPU、内存)隔离能力。

4.3、AM调优

  • 运行的一个大任务,map总数达到了上万的规模,任务失败,发现是ApplicationMaster(以下简称AM)反应缓慢,最终超时失败。
  • 失败原因是Task数量变多时,AM管理的对象也线性增长,因此就需要更多的内存来管理。AM默认分配的内存大小是1.5GB。

建议:任务数量多时增大AM内存

yarn.app.mapreduce.am.resource.mb

5、Namenode Full GC

JVM堆内存

  • JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。 
  • 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
  • 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
  • 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。

补充:

        JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。

5.1、对象分代

  • 新生成的对象首先放到年轻代Eden区,
  • 当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,
  • Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。
  • 经过多次Minor GC仍然存活的对象移动到老年代。
  • 老年代存储长期存活的对象,占满时会触发Major GC(Full GC),GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。

Minor GC : 清理年轻代
Major GC(Full GC) : 清理老年代,清理整个堆空间,会停止应用所有线程。

5.2、Jstat

        查看当前jvm内存使用以及垃圾回收情况

结果解释:

#C即Capacity 总容量,U即Used 已使用的容量
S0C: 当前survivor0区容量(kB)。
S1C: 当前survivor1区容量(kB)。
S0U: survivor0区已使用的容量(KB)
S1U: survivor1区已使用的容量(KB)

EC: Eden区的总容量(KB)
EU: 当前Eden区已使用的容量(KB)
OC: Old空间容量(kB)。
OU: Old区已使用的容量(KB)
MC: Metaspace空间容量(KB)
MU: Metacspace使用量(KB)
CCSC: 压缩类空间容量(kB)。
CCSU: 压缩类空间使用(kB)。
YGC: 新生代垃圾回收次数
YGCT: 新生代垃圾回收时间
FGC: 老年代 full GC垃圾回收次数
FGCT: 老年代垃圾回收时间
GCT: 垃圾回收总消耗时间

开启HDFS GC详细日志输出

编辑hadoop-env.sh

export HADOOP_LOG_DIR=/hadoop/logs/
  • 增加JMX配置打印详细GC信息
  • 指定一个日志输出目录;注释掉之前的ops
  • 增加新的打印配置
    #JMX配置
    export HADOOP_JMX_OPTS="-Dcom.sun.management.jmxremote.authenticate=false -
    Dcom.sun.management.jmxremote.ssl=false"
    export HADOOP_NAMENODE_OPTS="-Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-
    INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender}
    $HADOOP_NAMENODE_OPTS"
    export HADOOP_DATANODE_OPTS="-Dhadoop.security.logger=ERROR,RFAS
    $HADOOP_DATANODE_OPTS"
    
    export NAMENODE_OPTS="-verbose:gc -XX:+PrintGCDetails -
    Xloggc:${HADOOP_LOG_DIR}/logs/hadoop-gc.log \
    -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -
    XX:+PrintGCApplicationStoppedTime \
    -server -Xms150g -Xmx150g -Xmn20g -XX:SurvivorRatio=8 -
    XX:MaxTenuringThreshold=15 \
    -XX:ParallelGCThreads=18 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -
    XX:+UseCMSCompactAtFullCollection -XX:+DisableExplicitGC -
    XX:+CMSParallelRemarkEnabled \
    -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=70 -
    XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -
    XX:CMSMaxAbortablePrecleanTime=5000 \
    -XX:+UseGCLogFileRotation -XX:GCLogFileSize=20m -
    XX:ErrorFile=${HADOOP_LOG_DIR}/logs/hs_err.log.%p -
    XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${HADOOP_LOG_DIR}/logs/%p.hprof
    \
    "
    export DATENODE_OPTS="-verbose:gc -XX:+PrintGCDetails -
    Xloggc:${HADOOP_LOG_DIR}/hadoop-gc.log \
    -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -
    XX:+PrintGCApplicationStoppedTime \
    -server -Xms15g -Xmx15g -Xmn4g -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
    \
    -XX:ParallelGCThreads=18 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -
    XX:+UseCMSCompactAtFullCollection -XX:+DisableExplicitGC -
    XX:+CMSParallelRemarkEnabled \
    -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=70 -
    XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -
    XX:CMSMaxAbortablePrecleanTime=5000 \
    -XX:+UseGCLogFileRotation -XX:GCLogFileSize=20m -
    XX:ErrorFile=${HADOOP_LOG_DIR}/logs/hs_err.log.%p -
    XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${HADOOP_LOG_DIR}/logs/%p.hprof
    \
    "
    
    export HADOOP_NAMENODE_OPTS="$NAMENODE_OPTS $HADOOP_NAMENODE_OPTS"
    export HADOOP_DATANODE_OPTS="$DATENODE_OPTS $HADOOP_DATANODE_OPTS"

  • -Xms150g -Xmx150g :堆内存大小最大和最小都是150g
  • -Xmn20g :新生代大小为20g,等于eden+2*survivor,意味着老年代为150-20=130g。
  • -XX:SurvivorRatio=8 :Eden和Survivor的大小比值为8,意味着两个Survivor区和一个Eden区的比值为2:8,一个Survivor占整个年轻代的1/10
  • -XX:ParallelGCThreads=10 :设置ParNew GC的线程并行数,默认为8 +(Runtime.availableProcessors - 8) * 5/8 ,24核机器为18。
  • -XX:MaxTenuringThreshold=15 :设置对象在年轻代的最大年龄,超过这个年龄则会晋升到老年代
  • -XX:+UseParNewGC :设置新生代使用Parallel New GC
  • -XX:+UseConcMarkSweepGC :设置老年代使用CMS GC,当此项设置时候自动设置新生代为ParNew GC
  • -XX:CMSInitiatingOccupancyFraction=70 :老年代第一次占用达到该百分比时候,就会引发CMS的第一次垃圾回收周期。后继CMS GC由HotSpot自动优化计算得到。 

5.3、GC 日志解析

jstat命令输出

查看GC日志输出

  • ParNew: 16844397K->85085K(18874368K), 0.0960456 secs其中, 16844397K 表示GC前的新生代占用量, 85085K 表示GC后的新生代占用量,GC后Eden和一个Survivor为空,所以85085K 也是另一个Survivor的占用量。括号中的18874368K 是Eden+一个被占用Survivor的总和(18g)。 
  • 116885867K->100127390K(155189248K), 0.0961542 secs其中,分别是Java堆在垃圾回收前后的大小,和Java堆大小。说明堆使用为116885867K=111.47g,回收大小为100127390K=95.49g,堆大小为155189248K=148g(去掉其中一个Survivor),回收了16g空间.

 总结:

        在HDFS Namenode内存中的对象大都是文件,目录和blocks,这些数据只要不被程序或者数据的拥有者人为的删除,就会在Namenode的运 行生命期内一直存在,所以这些对象通常是存在在old区中,所以,如果整个hdfs文件和目录数多,blocks数也多,内存数据也会很大,如何降低Full GC的影响?

计算NN所需的内存大小,合理配置JVM

使用低卡顿G1收集器 

为什么会有G1呢?

   因为并发、并行和CMS垃圾收集器都有2个共同的问题:

  • 老年代收集器大部分操作都必须扫描整个老年代空间(标记,清除和压缩)。这就导致了GC随着Java堆空间而线性增加或减少
  • 年轻代和老年代是独立的连续内存块,所以要先决定年轻代和年老代放在虚拟地址空间的位置

G1垃圾收集器利用分而治之的思想将堆进行分区,划分为一个个的区域。

        G1垃圾收集器将堆拆成一系列的分区,这样的话,大部分的垃圾收集操作就只在一个分区内执行,从而避免很多GC操作在整个Java堆或者整个年轻代进行。

编辑hadoop-env.sh

export HADOOP_NAMENODE_OPTS="-server -Xmx220G -Xms200G -XX:+UseG1GC -
XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -
XX:+ParallelRefProcEnabled -XX:-ResizePLAB -XX:+PerfDisableSharedMem -XX:-
OmitStackTraceInFastThrow -XX:G1NewSizePercent=2 -XX:ParallelGCThreads=23 -
XX:InitiatingHeapOccupancyPercent=40 -XX:G1HeapRegionSize=32M -
XX:G1HeapWastePercent=10 -XX:G1MixedGCCountTarget=16 -verbose:gc -
XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -
XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=100M -
Xloggc:/var/log/hbase/gc.log -Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-
INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender}
$HADOOP_NAMENODE_OPTS"

注意:如果现在采用的垃圾收集器没有问题,就不要选择G1,如果追求低停顿,可以尝试使用G1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悠然予夏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值