Lucene5学习之Directory理解
Directory即Lucene中对索引目录的一个抽象,体现到API上,它被设计为一个抽象类,类里面定义了一些抽象方法,如listAll列出目录下所有文件,deleteFile(String name) 根据文件名称删除索引文件,这个都是文件的基本操作,其中比较重要的一个接口方法是makeLock,为什么要为索引目录加锁?其实就跟你上厕所为什么要锁门是一样一样滴?我没拉完你就给我乖乖等着。
BaseDirectory是Directory的一个子类,它默认实现了makeLock方法,
@Override public final Lock makeLock(String name) { return lockFactory.makeLock(this, name); }
这里的lockFactory仍然是一个抽象类,由子类传入传入具体的lockFactory工厂实现来创建不同的Lock实例FSDirectory是针对文件系统的一个Directory实现,即使用这个Directory就可以把索引文件存储到我们的文件系统里了。FSDirectory下面又有3个子类,分别是SimpleFSDirectory,NIOFSDirectory,MMapDirectory,
SimpleFSDirectory:它是基于文件系统的索引目录的一个简单实现,它在多线程环境下性能表现很差,不支持并发读写索引文件。
NIOFSDirectory:顾名思义,它就是使用NIO里的FileChannel文件通道来解决并发读写索引文件的,但它在windows平台下有个致命的BUG,官方建议在windows平台下使用RAFDirectory来代替NIOFSDirectory。
MMapDirectory:即基于内存映射的方式把文件load到内存来减少与IO的交互次数,从而提高IO性能,但这部分内存也存在隐患,因为JDK存在一个BUG,就是当IndexInput.close()时并不能有效的交还文件系统里索引文件的文件句柄,这就直接导致索引lock无法释放,一直直到GC回收这部分内存中潜在的对象时该句柄才会释放。由于文件句柄不能有效的立即释放,可能会导致你的硬盘空间得不到立即的释放,所以如果你的应用对于硬盘状况很敏感,这将是一个致命的定时炸蛋(连个zha弹都不让写,ITEye这是怎么了?),放在心里就行,反正暂时这个BUG未解决。(现在硬盘这么廉价,多浪费点硬盘空间也没啥)不过在linux系统上,即使句柄没有释放,当你删除索引文件时会提示是否在delete on last close,当你close时候还是会删除成功的,但索引文件还是会文件系统上占硬盘空间,而在windows平台上你只会得到一个Error,所以又是Windows,你懂的。
上面简单说了下各种FSDirectory的作用,下面简单说下各个Directory中比较重要的一些接口方法:
/** Just like {@link #open(Path)}, but allows you to * also specify a custom {@link LockFactory}. */ public static FSDirectory open(Path path, LockFactory lockFactory) throws IOException { if (Constants.JRE_IS_64BIT && MMapDirectory.UNMAP_SUPPORTED) { return new MMapDirectory(path, lockFactory); } else if (Constants.WINDOWS) { return new SimpleFSDirectory(path, lockFactory); } else { return new NIOFSDirectory(path, lockFactory); } }
FSDirectory类里的open方法是使用比较频繁的方法之一,其实内部就是根据用户的操作系统环境和使用的JDK来选择合适的Directory,
Constants.JRE_IS_64BIT即表示是否是64位的JDK,
MMapDirectory.UNMAP_SUPPORTED即表示是否支持Direct Buffer,什么叫Direct Buffer?其实Direct Buffer并不是直接分配在堆上的,Direct Buffer不受GC管理,即Direct Buffer是有操作系统来销毁的,但Direct Buffer上对象是由GC负责回收的,Direct Buffer读写之所以快,是因为减少数据拷贝到内核缓冲区的操作,但Direct Buffer是由操作系统负责销毁,所以代价也是很高的。那我们看看Lucene是如何判断是否支持Direct Buffer的?
/** * <code>true</code>, if this platform supports unmapping mmapped files. */ public static final boolean UNMAP_SUPPORTED; static { boolean v; try { Class.forName("sun.misc.Cleaner"); Class.forName("java.nio.DirectByteBuffer") .getMethod("cleaner"); v = true; } catch (Exception e) { v = false; } UNMAP_SUPPORTED = v; }
其实就是判断JDK里是否有sun.misc.Cleaner这个类,以及java.nio.DirectByteBuffer类是否有cleaner方法。
这两个类都是用于Direct buffer里对象清理工作的。
Oracle/Sun JDK 6中的HotSpot VM只会在年老代GC(full GC/major GC或者concurrent GC都算)的时候才会做reference processing,而在young GC/minor GC时不做。 也就是说,做full GC的话会做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。而如果很长一段时间里没做过GC或者只做了young GC的话则不会触发Cleaner的工作,那么就可能让本来已经死了的DirectByteBuffer关联的native memory得不到及时释放。
之所以要保证有上述两个类,就是确保DirectByteBuffer里的内存能得到释放以保证性能,这样你使用MMapDirectory才能体现它的优势又能避免它潜在的隐患。
还有一个比较重要的Directory就是RAMDirectory,就是内存索引目录,即把索引文件数据load到堆中,内部就是用new byte[1024]来缓存索引数据的,这适合于小量的索引文件,RAMDirectory设计之初就不是为百万级别的大数量的索引而设计的,因为它会在你的内存产生数以百万的byte[1024],从而导致频繁GC回收,影响性能,而且它在多线程并发环境下表现也很糟糕,官方建议是:大数据量的索引请使用MMapDirectory,RAMDirectory的使用场景是当你创建索引时可以先把少量的索引放入内存,再switch到FSDirectory flush到file system,利用内存减少与File System访问次数。
另外一个比较重要的Directory就是FileSwitchDirectory,它就是集NIODirectory与MMapDirectory优点与一身的Directory,NIODirectory是直接把文件放入heap buffer中,而MMapDirectory是把文件直接映射到Direct buffer,由于Heap buffer可使用量大而Direct buffer虽然读写速度快,但它受操作系统调度,开辟和销毁的代价太高,不宜过多使用,所以一般我们需要使用NIODirectory来读写比较大的索引文件,而用MMapDirectory来读写相对比较小的文件,两者结合互补你懂的。
同理Lock也有对应的3种实现,就不赘述了。
一般我们使用FSDirectory.open由api自动帮我们来选择合适的Directory即可,特殊情况下可以使用FileSwitchDirectory结合下两种Directory的优点。关于Directory就说这么多了,如果有哪里说的不正确,还望你提出来,谢谢!
如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,
或者加裙
一起交流学习!