您的位置:首页 > Web前端 > Node.js

NameNode内存优化---基于缓存相同文件名的方法

2014-01-28 17:54 204 查看
[b]NameNode内存优化---重用相同的文件名 [/b]
原创文章,转载请注明:博客园aprogramer

原文链接:NameNode内存优化---重用相同的文件名


众所周知,Hadoop集群的NameNode的内存会随着文件(目录)数和block数的增长而增长。当集群的规模比较大时,NameNode的内存使用量就会越来越大,发生FullGC时需要的时间也会越来越长,严重影响集群的可用性。当文件数很多时,文件名会有很多相同的,如果重用这些相同的文件名,就会减少NameNode内存的使用量。

1. Hadoop中有多少文件的文件名是重复的?

首先简单回顾一下NameNode是如何在内存中保存目录树的。在NameNode中文件用INodeFile类表示,而INodeFile继承自抽象类INode。而INode类有一个byte[] 类型的成员name,表示文件名。也就是说每个文件都有一个byte数组的成员保存文件名,当有两个文件的文件名完全一样时,就会new两个完全一样的byte数组,显然这是比较浪费的,而我们的优化思路就是把文件名相同的文件的公用一个byte数组,不要小看这个优化,当集群中有很多文件时,文件同名的可能性越大,优化的效果也就越明显。下表是Yahoo生产集群的统计数据:

File names used > 100000 times24
File names used between 10001 to 100000 times467
File names used between 1001 to 10000 times4335
File names used between 101 to 1000 times40031
File names used between 10 to 100 times403975
File names used between 2 to 9 times606579
File names used between 1 times4114531
Total file names5169942
从上表能够看出有516万的文件中有大约100万的文件重复了,在这重复的100万中又有40多万的文件名被重复用了10次以上,所以说文件名相同的情况还是很频繁的。

2. 如果缓存文件名大概能节省多少内存?

hadoop的oiv工具可以分析fsimage文件,计算出能够大约节省多少空间,算法如下:
1. 创建一个HashMap,key是String类型表示文件名,value是Integer类型表示这个文件名被几个文件使用,用来统计每个文件名被使用的次数。
2. 通过读fsimage文件遍历目录数中的每个Inode,如果是文件,并且hashmap中没有这个文件名,则用文件名作为key,1作为value存到hashmap中;如果 hashmap中没有则把hashmap中的对应项的value值加1;
3. 计算节省的空间:假设文件名filename被使用了N次(N>1),那么节省的空间数为:
(N-1)*(filename的长度+24),24是byte[]类型对象头部的overhead。
下面的是分析淘宝云梯NameNode image文件的结果:

bin/hadoop oiv -i  fsimage_0000000015589116819 -o ovils -p NameDistribution

Total unique file names 241275868
21 names are used by 2620832 files between 100000-214114 times. Heap savings ~89107574 bytes.
634 names are used by 20434805 files between 10000-99999 times. Heap savings ~703887328 bytes.
7593 names are used by 19447534 files between 1000-9999 times. Heap savings ~734908710 bytes.
49057 names are used by 16697480 files between 100-999 times. Heap savings ~729947272 bytes.
399464 names are used by 7812965 files between 10-99 times. Heap savings ~381085471 bytes.
638085 names are used by 4261035 files between 5-9 times. Heap savings ~223499710 bytes.
628335 names are used by 2513340 files 4 times. Heap savings ~118476621 bytes.
2550503 names are used by 7651509 files 3 times. Heap savings ~319960524 bytes.
3698086 names are used by 7396172 files 2 times. Heap savings ~230703725 bytes.

Total saved heap ~3531576935bytes.


通过分析fsimage,可以减少3531576935bytes=3.289g的内存空间,还是很可观的

3. 代码实现

下面的实现参考了Hadoop2.0版本,社区jira链接是HDFS-1110
因为要共用相同的文件名,而在Hadoop中文件名是用byte[]表示的,所以我们最终要缓存的是byte数组,又因为byte数组不适合作为HashMap的key,所以我们先对byte数组封装一下,重写了equals和hashCode方法。代码如下:

public class FSDirectoryNameCache {
// buffer this many elements in temporary queue
private static final int MAX_QUEUE_SIZE = 10000;

// actual cache
private final NameCache<ByteArray> nameCache;
private volatile boolean imageLoaded;

// initial caching utils
private ExecutorService cachingExecutor;
private List<Future<Void>> cachingTasks;
private List<INode> cachingTempQueue;

public FSDirectoryNameCache(int threshold) {
nameCache = new NameCache<ByteArray>(threshold);
imageLoaded = false;

// executor for processing temporary queue (only 1 thread!!)
cachingExecutor = Executors.newFixedThreadPool(1);
cachingTempQueue = new ArrayList<INode>(MAX_QUEUE_SIZE);
cachingTasks = new ArrayList<Future<Void>>();
}

/**
* Adds cached entry to the map and updates INode
*/
private void cacheNameInternal(INode inode) {
// Name is cached only for files
if (inode.isDirectory()) {
return;
}
ByteArray name = new ByteArray(inode.getLocalNameBytes());
name = nameCache.put(name);
if (name != null) {
inode.setLocalName(name.getBytes());
}
}

void cacheName(INode inode) {
if (inode.isDirectory()) {
return;
}
if (this.imageLoaded) {
// direct caching
cacheNameInternal(inode);
return;
}

// otherwise add it to temporary queue
cachingTempQueue.add(inode);

// if queue is too large, submit a task
if (cachingTempQueue.size() >= MAX_QUEUE_SIZE) {
cachingTasks.add(cachingExecutor
.submit(new CacheWorker(cachingTempQueue)));
cachingTempQueue = new ArrayList<INode>(MAX_QUEUE_SIZE);
}
}

/**
* Worker for processing a list of inodes.
*/
class CacheWorker implements Callable<Void> {
private final List<INode> inodesToProcess;

CacheWorker(List<INode> inodes) {
this.inodesToProcess = inodes;
}

@Override
public Void call() throws Exception {
for (INode inode : inodesToProcess) {
cacheNameInternal(inode);
}
return null;
}
}

/**
* Inform that from now on all caching is done synchronously.
* Cache remaining inodes from the queue.
* @throws IOException
*/
void imageLoaded() throws IOException {
if(cachingTasks == null) {
return;
}
for (Future<Void> task : cachingTasks) {
try {
task.get();
} catch (InterruptedException e) {
throw new IOException("FSDirectory cache received interruption");
} catch (ExecutionException e) {
throw new IOException(e);
}
}

// will not be used after startup
this.cachingTasks = null;
this.cachingExecutor.shutdownNow();
this.cachingExecutor = null;

// process remaining inodes
for(INode inode : cachingTempQueue) {
cacheNameInternal(inode);
}
this.cachingTempQueue = null;

this.imageLoaded = true;
}

void initialized() {
this.nameCache.initialized();
}

int size() {
return nameCache.size();
}

int getLookupCount() {
return nameCache.getLookupCount();
}

public void reset() {
nameCache.reset();
}
}


View Code

6. 异步实验结果及分析

加载淘宝云梯fsimage,测试结果如下所以:

优化前同步cache异步cache
fsimage加载时间898秒1102秒956秒
Heap Used115.252G112.148G112.147G
从实验结果上看异步cache和同步cache在内存使用上一致,都比优化前少了3G,但fsimage比同步cache少了146秒,比优化前只多了58秒,不到1分钟,优化效果明显。当然异步cache也有一个弱点,因为Inode先放入队列中,然后再交给size为1的FixedThreadPool处理,这样可能会造成任务堆积,生成大量的临时队列,造成内存碎片,严重时可能促发FullGC。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: