NameNode内存优化---基于缓存相同文件名的方法
2014-01-28 17:54
204 查看
[b]NameNode内存优化---重用相同的文件名 [/b]
原创文章,转载请注明:博客园aprogramer
原文链接:NameNode内存优化---重用相同的文件名
众所周知,Hadoop集群的NameNode的内存会随着文件(目录)数和block数的增长而增长。当集群的规模比较大时,NameNode的内存使用量就会越来越大,发生FullGC时需要的时间也会越来越长,严重影响集群的可用性。当文件数很多时,文件名会有很多相同的,如果重用这些相同的文件名,就会减少NameNode内存的使用量。
从上表能够看出有516万的文件中有大约100万的文件重复了,在这重复的100万中又有40多万的文件名被重复用了10次以上,所以说文件名相同的情况还是很频繁的。
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文件的结果:
通过分析fsimage,可以减少3531576935bytes=3.289g的内存空间,还是很可观的
因为要共用相同的文件名,而在Hadoop中文件名是用byte[]表示的,所以我们最终要缓存的是byte数组,又因为byte数组不适合作为HashMap的key,所以我们先对byte数组封装一下,重写了equals和hashCode方法。代码如下:
View Code
从实验结果上看异步cache和同步cache在内存使用上一致,都比优化前少了3G,但fsimage比同步cache少了146秒,比优化前只多了58秒,不到1分钟,优化效果明显。当然异步cache也有一个弱点,因为Inode先放入队列中,然后再交给size为1的FixedThreadPool处理,这样可能会造成任务堆积,生成大量的临时队列,造成内存碎片,严重时可能促发FullGC。
原创文章,转载请注明:博客园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 times | 24 |
File names used between 10001 to 100000 times | 467 |
File names used between 1001 to 10000 times | 4335 |
File names used between 101 to 1000 times | 40031 |
File names used between 10 to 100 times | 403975 |
File names used between 2 to 9 times | 606579 |
File names used between 1 times | 4114531 |
Total file names | 5169942 |
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 Used | 115.252G | 112.148G | 112.147G |
相关文章推荐
- 一种基于主键索引和缓存机制的大数据表同步方法
- 缓存篇~第六回 Microsoft.Practices.EnterpriseLibrary.Caching实现基于方法签名的数据集缓存
- 详解基于LRU缓存的LruCache类及其在Android内存优化中的应用
- AMFPHP中,类名必须和php文件名相同,类中的方法名不能和类名相同。否则会出错。
- Python基于checksum计算文件是否相同的方法
- 基于vue中keep-alive缓存问题的解决方法
- 微信小程序基于本地缓存实现点赞功能的方法
- 一种基于精简配置系统的块设备缓存装置及其方法
- 基于nginx实现缓存功能及uptream模块详细使用方法
- 基于spring 方法级缓存的多种实现
- PHP基于文件存储实现缓存的方法
- 缓存篇~第七回 Redis实现基于方法签名的数据集缓存(可控更新,分布式数据缓存)
- Android基于SoftReference缓存图片的方法
- 工具方法:下载文件时碰到相同文件,文件名增加(1) 如果有(1)则下载文件名后缀(2)
- log4j的main方法打和web的tomcate启动打印方法文件名日志相同,路径不同并非没有打日志,而是在别的路径下
- 原创:一个基于window.opener属性的客户端缓存方法。
- Python基于checksum计算文件是否相同的方法
- PHP基于文件存储实现缓存的方法
- 下载文件时,文件名相同会被覆盖解决方法