文章转载自:
https://www.cnblogs.com/xiaoliang-y/p/4920170.html
1. HDFS均衡操作原理
HDFS默认的块的副本存放策略是在发起请求的客户端存放一个副本,如果这个客户端在集群以外,那就选择一个不是太忙,存储不是太满的节点来存放,第二个副本放在与第一个副本相同的机架但是不同节点上,第三个放在与第二个和第一个副本不同的机架上,原则是尽量避免在相同的机架上放太多的副本。
随着时间的推移,在各个DataNode节点上的数据块会分布的越来越不均衡。如果集群不均衡的程度很严重,会降低Mapreduce的使用性能,导致部分DataNode节点相对而言变得更加繁忙。所以,应该尽量的避免出现这种情况。
HDFS的 Balancer 类,是为了实现HDFS的负载调整而存在的。Balancer类是以一个独立的进程存在的,可以独立的运行和配置。它NameNode节点进行通信,获取各个DataNode节点的负载状况,从而进行调整DataNode上的Block的分布。主要的调整其实就是一个操作,将一个数据块从一个服务器搬迁到另一个服务器上。Balancer会向相关的目标 DataNode 节点 发出一个DataTransferProtocol.OP_REPLACE_BLOCK 消息,接收到这个消息的DataNode节点,会将从源DataNode节点传输来的数据块写入本地,写成功后,通知NameNode,删除源DataNode上的同一个数据块,直到集群达到均衡为止,即每个DataNode的使用率(该节点已使用的空间和空间容量之间的百分比值)和集群的使用率(集群中已使用的空间和集群的空间容量之间的百分比值)非常接近,差距不超过均衡时给定的阈值。
其中,一个块是否可以被移动,要满足三个条件:
(1)正在被移动或者已经被移动的块,不会重复移动
(2)一个块如果在源节点和目标节点上都有其副本,则此块不会被移动;
(3)移动不会减少一个块所在的机架的数目;
可见,由于上述等条件的限制,均衡操作并不能使得HDFS达到真正意义上的均衡,它只能是尽量的减少不均衡。
均衡操作依靠一个均衡操作服务器、NameNode的代理和DataNode来实现,其逻辑流程如下:
其中,
Step1:Rebalance Server从Name Node中获取所有的Data Node情况,即每一个Data Node磁盘使用情况;
Step2: Rebalance Server计算哪些Dataode节点需要将数据移动,哪些Dataode节点可以接受移动的块数据,并且从NameNode中获取需要移动的数据分布情况;
Step3: Rebalance Server计算出来可以将哪一台Dataode节点的block移动到另一台机器中去;
Step4、5、6:需要移动block的Dataode节点将数据移动到目标DataNode节点上去,同时删除自己节点上的block数据;
Step7: Rebalance Server获取到本次数据移动的执行结果,并继续执行这个过程,一直到没有数据可以移动或者HDFS集群以及达到了平衡的标准为止;
在step2中,HDFS会把当前的DataNode节点根据阈值的设定情况划分到四个链表中:
- over组:此组中的DataNode的均满足
DataNode_usedSpace_percent > Cluster_usedSpace_percent + threshold; - above组:此组中的DataNode的均满足
Cluster_usedSpace_percent + threshold > DataNode_ usedSpace _percent > Cluster_usedSpace_percent; - below组:此组中的DataNode的均满足
Cluster_usedSpace_percent > DataNode_ usedSpace_percent > Cluster_ usedSpace_percent – threshold; - under组:此组中的DataNode的均满足
Cluster_usedSpace_percent – threshold > DataNode_usedSpace_percent;
用一个示例图表示:
在移动块的时候,会把over组和above组中的块向below组和under组移动,直到均衡状态或者达到均衡退出的条件为止。
总得来说,均衡操作的步骤可以分为4步:
- 从namenode获取datanode磁盘使用情况;
- 计算哪些节点需要把哪些数据移动到哪里;
- 分别移动,完成后删除旧的block信息;
- 循环执行,直到达到平衡标准;
2. HDFS均衡操作的启动
使用HDFS的balancer命令,可以配置一个Threshold来平衡每一个DataNode磁盘利用率。命令如下:
start-balancer.sh -threshold 8
运行之后,会有Balancer进程出现:
上述命令设置了Threshold为8%,那么执行balancer命令的时候,首先统计所有DataNode的磁盘利用率的均值,然后判断如果某一个DataNode的磁盘利用率超过这个均值Threshold,那么将会把这个DataNode的block转移到磁盘利用率低的DataNode,这对于新节点的加入来说十分有用。Threshold的值为1到100之间,不显示的进行参数设置的话,默认是10。
范围超出之后,会有异常抛出:
java.lang.IllegalArgumentException: Number out of range: threshold = 0.07
at org.apache.hadoop.hdfs.server.balancer.Balancer$Cli.parse(Balancer.java:1535)
at org.apache.hadoop.hdfs.server.balancer.Balancer$Cli.run(Balancer.java:1510)
at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
at org.apache.hadoop.hdfs.server.balancer.Balancer.main(Balancer.java:1582)
2012-12-19 16:28:33,299 ERROR org.apache.hadoop.hdfs.server.balancer.Balancer: Exiting balancer due an exception
java.lang.IllegalArgumentException: Number out of range: threshold = 110.0
at org.apache.hadoop.hdfs.server.balancer.Balancer$Cli.parse(Balancer.java:1535)
at org.apache.hadoop.hdfs.server.balancer.Balancer$Cli.run(Balancer.java:1510)
at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
at org.apache.hadoop.hdfs.server.balancer.Balancer.main(Balancer.java:1582)
如果参数值设置的越小,花费的时间就越长。使用此命令时,会反复的从磁盘使用率高的节点上,把块转移到磁盘使用率低的磁盘上,每次移动不超过10G大小,每次移动不超过20分钟。
在做均衡的时候,会对网络带宽有影响,可在配置文件中对均衡操作的带宽做限制:
<property>
<name>dfs.balance.bandwidthPerSec</name>
<value>1048576</value>
<description>
Specifies the maximum bandwidth that each datanode can utilize for the balancing purpose in term of the number of bytes per second.
</description>
</property>
若不设置,则balance操作时,速度默认为1M/S大小。参数重启时生效。不允许在集群中使用多个均衡同时操作。
3. HDFS均衡操作的退出
除了在命令行直接使用stop-balancer.sh脚本来执行退出均衡操作之外,当发生以下几种情况时,当前执行的均衡操作也会退出:
- 集群已经达到均衡状态;
- 没有块可以再被移动;
- 连续五次迭代操作时没有块移动;
- 和NameNode通信时出现IOException;
- 另外一个均衡操作启动;
4. 实例分析
均衡操作之前DataNode节点块分布情况:
在sbin目录下执行命令./start-balancer.sh -threshold 5
均衡过程中:
开始做均衡操作时,会有如下日志打印出:
2012-12-28 10:08:55,667 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Using a threshold of 5.0
2012-12-28 10:08:55,668 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: namenodes = [hdfs://goon]
2012-12-28 10:08:55,669 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: p = Balancer.Parameters[BalancingPolicy.Node, threshold=5.0]
其中会标明对哪些nameservice进行均衡,同时对输入的参数进行说明,默认的策略是BalancingPolicy.Node,表示均衡的对象是DataNode,否则是对块池做均衡。
之后,会计算需要移动的块,移动的字节数,块的源地址和目标地址:
2012-12-28 10:08:57,200 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: 1 over-utilized: [Source[10.28.169.126:50010, utilization=25.807874799394025]]
2012-12-28 10:08:57,200 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: 1 underutilized: [BalancerDatanode[10.28.169.122:50010, utilization=9.395091359283992]]
2012-12-28 10:08:57,201 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Need to move 2.75 GB to make the cluster balanced.
2012-12-28 10:08:57,202 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Decided to move 2.46 GB bytes from 10.28.169.126:50010 to 10.28.169.122:50010
2012-12-28 10:08:57,203 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Will move 2.46 GB in this iteration
此时:
刚开始均衡操作时,其进程所占资源:
同时,可以观察到会有块的移动,从datanode0移动到sdc2和Tdatanode0,在NN会有如下日志(hadoop-hdfs-balancer-Tdatanode0.log):
2012-12-28 10:08:57,724 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Moving block 365766470964968392 from 10.28.169.126:50010 to 10.28.169.122:50010 through 10.28.169.225:50010 is succeeded.
2012-12-28 10:08:57,724 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Moving block 7346867188539736438 from 10.28.169.126:50010 to 10.28.169.122:50010 through 10.28.169.225:50010 is succeeded.
2012-12-28 10:08:57,724 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: Moving block -6882244909405313690 from 10.28.169.126:50010 to 10.28.169.122:50010 through 10.28.169.225:50010 is succeeded.
由于在Balancer类中对线程池做了限制,
final static private int MOVER_THREAD_POOL_SIZE = 1000;
所以最多可以有1000个线程并发块移动操作,但是向一个DN进行移动块时,最多5个块并发移动。
由于机器资源的限制,当均衡操作创建的线程数量达到900多的时候,就无法再创建线程,并有如下错误:
2012-12-28 10:19:32,905 WARN org.apache.hadoop.hdfs.server.balancer.Balancer: Dispatcher thread failed
java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:640)
at java.util.concurrent.ThreadPoolExecutor.addIfUnderCorePoolSize(ThreadPoolExecutor.java:703)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:652)
at org.apache.hadoop.hdfs.server.balancer.Balancer$PendingBlockMove.scheduleBlockMove(Balancer.java:402)
at org.apache.hadoop.hdfs.server.balancer.Balancer$PendingBlockMove.access$3500(Balancer.java:236)
at org.apache.hadoop.hdfs.server.balancer.Balancer$Source.dispatchBlocks(Balancer.java:746)
at org.apache.hadoop.hdfs.server.balancer.Balancer$Source.access$2000(Balancer.java:591)
at org.apache.hadoop.hdfs.server.balancer.Balancer$Source$BlockMoveDispatcher.run(Balancer.java:598)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
查看均衡操作所产生的线程会被阻塞:
Thread 15688: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Interpreted frame)
- java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=156 (Interpreted frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=1987 (Interpreted frame)
- java.util.concurrent.LinkedBlockingQueue.take() @bci=29, line=399 (Interpreted frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=78, line=947 (Interpreted frame)
- java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=18, line=907 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=662 (Interpreted frame)
Thread 15687: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Interpreted frame)
- java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=156 (Interpreted frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=1987 (Interpreted frame)
- java.util.concurrent.LinkedBlockingQueue.take() @bci=29, line=399 (Interpreted frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=78, line=947 (Interpreted frame)
- java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=18, line=907 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=662 (Interpreted frame)
同时,由于无法创建线程,RPC通信也会阻塞,
2012-12-28 10:37:28,710 WARN org.apache.hadoop.io.retry.RetryInvocationHandler: Exception while invoking renewLease of class ClientNamenodeProtocolTranslatorPB. Trying to fail over immediately.
2012-12-28 10:37:28,716 WARN org.apache.hadoop.io.retry.RetryInvocationHandler: Exception while invoking renewLease of class ClientNamenodeProtocolTranslatorPB after 1 fail over attempts. Trying to fail over immediately.
2012-12-28 10:37:28,721 WARN org.apache.hadoop.io.retry.RetryInvocationHandler: Exception while invoking renewLease of class ClientNamenodeProtocolTranslatorPB after 2 fail over attempts. Trying to fail over immediately.
。。。
。。。
2012-12-28 10:37:28,795 WARN org.apache.hadoop.io.retry.RetryInvocationHandler: Exception while invoking renewLease of class ClientNamenodeProtocolTranslatorPB after 14 fail over attempts. Trying to fail over immediately.
2012-12-28 10:37:28,802 WARN org.apache.hadoop.io.retry.RetryInvocationHandler: Exception while invoking class org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.renewLease. Not retrying because failovers (15) exceeded maximum allowed (15)
java.io.IOException: Failed on local exception: java.io.IOException: Couldn’t set up IO streams; Host Details : local host is: “Tdatanode0/10.28.169.126″; destination host is: “sdc1″:9000;
at org.apache.hadoop.net.NetUtils.wrapException(NetUtils.java:760)
at org.apache.hadoop.ipc.Client.call(Client.java:1168)
at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:202)
at $Proxy11.renewLease(Unknown Source)
at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.renewLease(ClientNamenodeProtocolTranslatorPB.java:452)
at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.hadoop.io.retry.RetryInvocationHandler.invokeMethod(RetryInvocationHandler.java:164)
at org.apache.hadoop.io.retry.RetryInvocationHandler.invoke(RetryInvocationHandler.java:83)
at $Proxy12.renewLease(Unknown Source)
at org.apache.hadoop.hdfs.DFSClient.renewLease(DFSClient.java:613)
at org.apache.hadoop.hdfs.LeaseRenewer.renew(LeaseRenewer.java:411)
at org.apache.hadoop.hdfs.LeaseRenewer.run(LeaseRenewer.java:436)
at org.apache.hadoop.hdfs.LeaseRenewer.access$700(LeaseRenewer.java:70)
at org.apache.hadoop.hdfs.LeaseRenewer$1.run(LeaseRenewer.java:297)
at java.lang.Thread.run(Thread.java:662)
Caused by: java.io.IOException: Couldn’t set up IO streams
at org.apache.hadoop.ipc.Client$Connection.setupIOstreams(Client.java:640)
at org.apache.hadoop.ipc.Client$Connection.access$1700(Client.java:220)
at org.apache.hadoop.ipc.Client.getConnection(Client.java:1217)
at org.apache.hadoop.ipc.Client.call(Client.java:1144)
… 15 more
Caused by: java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:640)
at org.apache.hadoop.ipc.Client$Connection.setupIOstreams(Client.java:633)
… 18 more
对每个NameNode都会尝试两次连接 set up IO streams,周期性的进行连接请求,即NN1连接不成功(尝试连接2次),然后就去尝试连接NN2(若尝试连接2次,不成功,就再去连接NN1),每次尝试连接NN后,都会有15次的更新租约尝试,当然,连接不上NN,租约更新也是失效的。
同时,由于此均衡是在NN上操作的,此NN节点会出现僵死状态(在NN上进行均衡仅为测试用,真正使用时不会在NN上),通过web不能访问,因为操作不能再创建新的线程。并且,如果当前是在active节点上进行的操作,而且在配置文件中配置了允许HA自动切换,那么此时会发生HA自动切换,即当前的standby节点变为active节点。
可以在linux下更改参数进行扩大线程创建的数量:
更改之前:
通过命令ulimit -u 65535临时修改,或者通过修改配置文件永久修改:/etc/security/limits.conf
同时也可以修改栈空间所占的大小,默认是10240字节,可以通过ulimit -s 1024修改为1024字节,减少每个堆栈的使用空间,也可以增加线程的数量。
另外,在均衡过程中,Balancer的内存使用情况如下:
所占内存大概是140M左右。在运行过程中,会产生一些垃圾文件,因为节点硬件的限制,所以必须对缓存进行清理:
若出现socket连接异常,则块移动失败,则会提示:
2012-12-21 13:10:42,915 WARN org.apache.hadoop.hdfs.server.balancer.Balancer: Error moving block -5503762968190078708 from 10.28.169.225:50010 to 10.28.169.122:50010 through 10.28.169.225:50010: Connection reset
均衡之后:
当出现日志:
2012-12-28 12:20:00,430 INFO org.apache.hadoop.net.NetworkTopology: Adding a new node: /default-rack/10.28.169.122:50010
2012-12-28 12:20:00,431 INFO org.apache.hadoop.net.NetworkTopology: Adding a new node: /default-rack/10.28.169.225:50010
2012-12-28 12:20:00,431 INFO org.apache.hadoop.net.NetworkTopology: Adding a new node: /default-rack/10.28.169.126:50010
2012-12-28 12:20:00,431 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: 0 over-utilized: []
2012-12-28 12:20:00,431 INFO org.apache.hadoop.hdfs.server.balancer.Balancer: 0 underutilized: []
均衡操作结束,因为Over组没有DataNode,Under组也没有DataNode,此时:
在均衡操作之前:
clusterAvg1
=集群dfs已使用空间/集群总空间
=(12.7+12.19+4.62)/(49.22+49.22+49.22)
=19.99%
我们在做均衡时所设定的阈值为5,即百分之5,clusterAvg1-5%=14.99%,clusterAvg1+5%=24.99%,而Tdatanode0的空间使用率是25.81%,超过 24.99% ,属于over节点,sdc2的空间使用率是9.4%,低于14.99%,属于under节点,datanode0的空间使用率是24.76%,高于14.99%但是低于24.99%,属于above节点,符合均衡操作的发生条件。
集群的空间平均使用率为:
clusterAvg2
=集群dfs已使用空间/集群总空间
=(12.7+11.46+8.91)/(49.22+49.22+49.22)
=22.41%
我们在做均衡时所设定的阈值为5,即百分之5,clusterAvg2-5%=17.41%,clusterAvg2+5%=27.41%,所有的DN的空间使用率都在17.41%–27.41%中间,说明集群已经达到均衡状态。