散仙今天就从源码的角度来分析下Lucene的根基Directory的实现,在此以前,咱们先来看下Directory家族的层级分布图。 java
从上图中,咱们能够看出Directory共有11个直接或者间接的子类,不一样的子类的做用和功能不同,那么Directory做为此继承图的顶级父类,在Lucene中确实发挥重要的根基做用,就像Hadoop的根基是HDFS同样,Directory肩负着索引存储的重任,若是没有存储,那么检索就无从谈起了,虽然咱们常常称全文检索,搜索引擎什么的,其实它们的背后,Directory才是默默无闻的”雷锋“。
web
下面就来详细的剖析下Directory的核心实现。
Directory是由lucene中的一些列索引文件组成的目录,一个典型索引文件结构图的截图以下:多线程
而Directory的做用,就是负责管理这些索引文件,包括数据的读取和写入,以及索引文件的添加,删除和合并。从这样的角度来分析,Directory更像一个系统的管理员,下面,散仙再具体的分析下一些核心方法的做用。
咱们都知道Lucene的索引体系,支持读共享,写独占的方式来访问索引目录,也就是说,它容许多个线程实例同时并发的读取,而不容许多个线程同时写入,你们可能会有疑问,为何不支持多线程写入呢?这实际上是由于索引目录有本身的某一时刻的内部状态,好比说文件指针,而多线程写入时,会形成指针混乱,从而引发索引结构损坏或某些数据丢失,因此lucene任什么时候候都禁止有多个线程并发的写入索引,即便是多线程写,每次也只能经过队列的方式,一次只容许一个线程操做索引,按这样的状况分析,多线程写入与单线程写入,在性能上的提高,并非明显的,那么lucene又是怎么控制一次只能有一个线程写入呢,打开Directory的源码,咱们就会发现,它实际上是在内部维护了一个锁的实例,经过加锁方式,来禁止后来线程的写入操做,固然锁的做用不只仅是防止并发写入,它还能够经过锁名字来判断,这两份索引是否为同一份索引,那么若是咱们想使用多线程来提高写入速度,一个折中的办法就是,每一个线程写一份目录,最后在对这些目录,进行合并,下面给出了一些源码中锁的实现方法
并发
protected LockFactory lockFactory;//锁实现,只能由子类覆盖 //设置锁名 public Lock makeLock(String name) { return lockFactory.makeLock(name); } //清除锁 public void clearLock(String name) throws IOException { if (lockFactory != null) { lockFactory.clearLock(name); } }
下面咱们来分析下Directory源码中另一个变量isOpen的做用
oop
//注意,使用的是volatile关键字修饰 volatile protected boolean isOpen = true;
isOpen是用来判断当前的Directory实例,在内存中的状态,它使用的是volatile 关键字修饰的,被此变量修饰的内容,JVM虚拟机读取的时候会直接在主存中读取该变量的值,而不会在各个线程的本地内存中读,这样一来,当并发读的时候,若是Directory实例关闭了,那么各个读的线程会当即获取最新的状态,若是不作处理的话,将会抛出一个目录实例关闭的异常。isOpen 确保了索引在并发读的时候,各个线程实例获取Directory状态的一致性。
性能
private static final class SlicedIndexInput extends BufferedIndexInput { IndexInput base; long fileOffset; long length; SlicedIndexInput(final String sliceDescription, final IndexInput base, final long fileOffset, final long length) { this(sliceDescription, base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE); } SlicedIndexInput(final String sliceDescription, final IndexInput base, final long fileOffset, final long length, int readBufferSize) { super("SlicedIndexInput(" + sliceDescription + " in " + base + " slice=" + fileOffset + ":" + (fileOffset+length) + ")", readBufferSize); this.base = base.clone(); this.fileOffset = fileOffset; this.length = length; }
接下来,来分析Directory的静态常量内部类SlicedIndexInput的做用,Lucene的索引文件是很是松散的,不一样类型的数据存储在不一样的文件里,咱们能够经过文件名,来单独读取指定索引文件的内容,一样道理咱们也能够,在写入信息时候,单独写入某部分数据的信息,这样一来,就避免了操做整个目录的可能,按需所用,从必定程度上来讲,这样的设计提高了性能,保证了数据的稳定与可靠性,虽然也从某种程度上加大了Directory目录管理的复杂度,但这些都是微不足道的。
SlicedIndexInput这个类的做用保证了Lucene能够单独读取部分索引文件的内容,注意这些内容都不是最原始的数据,而是SlicedIndexInput克隆的一份副本,这样一来在并发读的环境下是很是有利的,每一个线程都会从主存中load一份副本出来。在咱们的源码中,咱们并无发现它具备深度克隆的功能,可是经过一系列继承的追踪,咱们发现,SlicedIndexInput==》BufferedIndexInput==》IndexInput==》DataInput,在最后的这个父类中实现了Cloneable和Closeable接口,从而确保保证了SlicedIndexInput能够正常的工做,以及释放一些占用的IO资源。
除了上面几个比较重要的做用外,Directory还提供了,其余的一些文件管理功能,例如获取全部的索引文件信息,删除一个索引文件,获取一个索引文件的大小,索引的备份,等等在这里散仙,就不给出演示了,此篇文章重点分析的Directory的功能和做用,后续的文章,散仙会重点分析它的一些子类的实现和功能。
this