Hadoop的磁盘写入策略引发的问题

DataNode挂载的磁盘或者DataNode节点挂载多个磁盘,如果存在一些磁盘大小不一样,数据在落盘时就可能会出现磁盘使用率不均匀的情况,容量较小的盘容易被写满,而容量大的盘还剩很多空间。磁盘写满后,影响Hadoop集群的正常工作。国庆第一天,线上集群就报出了JournalNode挂掉的异常情况,经查是由于2T的磁盘被写满,JournalNode无法再写入数据。当时采取了临时的措施,删掉HBase和Hive中不用,占大量空间的表。磁盘使用率下降一部分后,重新启动JournalNode。

集群中每个DataNode都挂载了两个硬盘,分别为2T和4T的,2T基本都被写满,而4T的才50%多。是什么造成了这种数据落盘时的不均匀情况?本主要文调研了Hadoop的数据两种写入磁盘的策略,并分析了两种策略的主要源码实现,最后总结解决此次异常的经验。

两种写入策略

循环选取策略

循环选取策略是在hfds1.0中实现的,hdfs2.x默认沿用hfds1.x的方式
hdfs2.0默认沿用hfds1.0的方式,按照循环的策略,数据会均匀的落在不同大小的盘上,大磁盘和小磁盘存储的块是一样的,导致小的磁盘最先被写满。

可用空间策略

hdfs2.0也提供了另一种策略,将数据优先写入具有最大可用空间。通过一个概率计算出选择写入的磁盘,磁盘剩余空间大的将会获得更大的写入概率,这样磁盘的使用率就会相对均匀。

两种方案的对比图如下,图来源于链接,能够很清楚的看出两种策略的不同。

hdfs3.0提供了一个在线磁盘均衡器diskbalancer ,能在不停机的情况下,对数据进行均衡操作。但是hadoop3.0仍是一个测试版本,因此不可能进行升级。

源码分析

循环选取策略

循环选取的策略很简单,循环扫描整个Volumes,如果availableVolumeSize大于blockSize ,即返回该volume。为了保证每次选择的起点都不是从头开始,导致数据写满一个盘后再写另一个盘,使用了一个curVolumes定位器来防止这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int curVolume = curVolumes[curVolumeIndex] < volumes.size()
? curVolumes[curVolumeIndex] : 0;
int startVolume = curVolume;
long maxAvailable = 0;
while (true) {
final V volume = volumes.get(curVolume);
curVolume = (curVolume + 1) % volumes.size();
long availableVolumeSize = volume.getAvailable();
if (availableVolumeSize > blockSize) {
curVolumes[curVolumeIndex] = curVolume;
return volume;
}
if (availableVolumeSize > maxAvailable) {
maxAvailable = availableVolumeSize;
}
if (curVolume == startVolume) {
throw new DiskOutOfSpaceException("Out of space: "
+ "The volume with the most available space (=" + maxAvailable
+ " B) is less than the block size (=" + blockSize + " B).");
}
}

可用空间策略

1、通过计算最大剩余空间与最小剩余空间的差值,然后与阈值dfs.datanode.available-space-volume-choosing-policy.balanced-space-threshold进行对比,默认为10G,如果小于该值,将使用循环写入策略,如果不小于该值,则使用最大可用空间策略。

1
2
3
4
5
6
7
8
9
public boolean areAllVolumesWithinFreeSpaceThreshold() {
long leastAvailable = Long.MAX_VALUE;
long mostAvailable = 0;
for (AvailableSpaceVolumePair volume : volumes) {
leastAvailable = Math.min(leastAvailable, volume.getAvailable());
mostAvailable = Math.max(mostAvailable, volume.getAvailable());
}
return (mostAvailable - leastAvailable) < balancedSpaceThreshold;
}

2、通过与leastAvailable + balancedSpaceThreshold比较,将volume划分为两类集合。一类lowAvailableVolumes相对最小,一类highAvailableVolumes相对最大。

1
2
3
4
5
6
7
8
9
10
11
public List<AvailableSpaceVolumePair> getVolumesWithHighAvailableSpace() {
long leastAvailable = getLeastAvailableSpace();
List<AvailableSpaceVolumePair> ret = new ArrayList<AvailableSpaceVolumePair>();
for (AvailableSpaceVolumePair volume : volumes) {
//leastAvailable为所有Volume中容量最小的
if (volume.getAvailable() > leastAvailable + balancedSpaceThreshold) {
ret.add(volume);
}
}
return ret;
}

3、根据dfs.datanode.available-space-volume-choosing-policy.balanced-space-preference-fraction的大小(默认为0.75f)和lowAvailableVolumes,highAvailableVolumes的大小计算出一个两类Volumes选取的概率。这里没有直接使用0.75f,而是考虑到了两类Volume的数量的影响,如果highAvailableVolumes的数量大于lowAvailableVolumes,则计算出的Volume选取概率将大于0.75。反之则小。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获得相对最大和相对最小的磁盘集合,将volume划分为两类
List<V> highAvailableVolumes = extractVolumesFromPairs(
volumesWithSpaces.getVolumesWithHighAvailableSpace());
List<V> lowAvailableVolumes = extractVolumesFromPairs(
volumesWithSpaces.getVolumesWithLowAvailableSpace());
// 算出一个相对概率
float preferencePercentScaler =
(highAvailableVolumes.size() * balancedPreferencePercent) +
(lowAvailableVolumes.size() * (1 - balancedPreferencePercent));
float scaledPreferencePercent =
(highAvailableVolumes.size() * balancedPreferencePercent) /
preferencePercentScaler;

4、最后随机生成一个概率与scaledPreferencePercent对比,从而决定从highAvailableVolumes,还是lowAvailableVolumes中选择Volume。这里同样使用了循环顺序选择策略。

1
2
3
4
5
6
7
8
9
10
if (mostAvailableAmongLowVolumes < replicaSize ||
random.nextFloat() < scaledPreferencePercent) {
// 在high volume中循环选择一个
volume = roundRobinPolicyHighAvailable.chooseVolume(
highAvailableVolumes, replicaSize);
} else {
// 在low volume中循环选择一个
volume = roundRobinPolicyLowAvailable.chooseVolume(
lowAvailableVolumes, replicaSize);
}

汇总:程序根据设定的阈值判断使用循环顺序策略还是最大可用空间策略,如果使用最大可用空间策略,将所有的Volume分为两类,根据设置的选取概率和每一类的数量计算出每一类的选取概率,然后在选取到的集合中再使用循环顺序策略。

解决方法

由于循环策略造成磁盘不均的解决方法如下:
1、数据清理:此方法属于紧急措施:清理掉hdfs中不用的数据
2、数据压缩:手动压缩部分数据,对于HBase可使用GZ压缩方式,能快速有效的降低磁盘使用率
3、数据移盘:手动进行数据的移动,将部分数据由写满的盘移动到其它盘中

主要有三步操作:
1、关闭DataNode节点
2、使用mv命令移动数据,要绝对保证移动后的数据相对目录与移动前一致,如移动前data/1/dfs/dn/current/BP-1788246909-172.23.1.202-1412278461680/current/finalized/subdir0/subdir1/,移动后为data/5/dfs/dn/current/BP-1788246909-172.23.1.202-1412278461680/current/finalized/subdir0/subdir1/
3、重启DataNode

可以参考 https://wiki.apache.org/hadoop/FAQ#On_an_individual_data_node.2C_how_do_you_balance_the_blocks_on_the_disk.3F
4、通过上述步骤后,可以选择切换到可用空间策略上。

总结

经过此次异常情况,我重新梳理了问题的过程,分析薄弱的环节,加强的对Hadoop磁盘的监控,增加对异常的处理手段。同时也对Hadoop的磁盘写入策略进行了调研,了解问题产生的原因,才能更好的解决问题。

参考资料

https://issues.apache.org/jira/browse/HDFS-1804
https://www.iteblog.com/archives/1905.html
https://wiki.apache.org/hadoop/FAQ#On_an_individual_data_node.2C_how_do_you_balance_the_blocks_on_the_disk.3F