Lucene4.3进阶开发之乱世丛生(二)

    在本篇将重点介绍下Directory的一个很重要的子类FSDirectory,为什么说此类非常重要呢?如果你是正在使用lucene的开发者,那么就知道,我们经常使用的一行代码:

Directory directory=FSDirectory.open(new File(indexPath))

    通过这行代码,我们可以获取一个Directory的子类文件存储目录,然后我们对索引的一些操作,都是以这个子类的文件目录为基础的,下面从源码的角度剖析下FSDirectory这个类的作用,在此之前用一个表格来介绍下lucene存储索引的几种方式:

Lucene4.3进阶开发之乱世丛生(二)

上面的几种存储方式是lucene目前为止,能够支持良好的格式,那么今天,就要介绍FSDirectory这类方式,就是上面图标中,第三类基于文件系统存储方式的根基,FSDirectory并不是一个具体的文件目录,通常情况下,我们使用的是FSDirectory下一个具体的子类(MMapDirectory、SimpleFSDirectory、NIOFSDirectory)来作为我们的索引目录,那么我们可能有个很大的疑惑,我们在实际开发者大部分时候并没有直接指定具体使用的是哪个目录,为什么我们还能正常使用它呢?

先来看下经常使用的那个FSDirectory的open方法源码是怎么实现的:

/** Just like {@link #open(File)}, but allows you to
   *  also specify a custom {@link LockFactory}. */
  public static FSDirectory open(File path, LockFactory lockFactory) throws IOException {
    if ((Constants.WINDOWS || Constants.SUN_OS || Constants.LINUX)
          && 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);
    }
  }

事实上,我们通过open方法,lucene底层通常跟我们的jre的为啥是直接相关的,大多数的Solaris、Linux和Windows64位系统的jre会返回MMapDirectory,而其他一些位数的jre,如32位的jre在Windows上会返回SimpleFSDirectory,剩余的部分会直接使用NIOFSDirectory来存储索引。那么这三种方式有什么不同呢?总结如下:

1、SimpleFSDirectory:这个类简单的实现使用RandomAccessFile来完成索引的存储,读写速度一般,并发性很差,在多个线程同时访问索引时,会造成线程同步,从而大大降低了性能,当然,如果我们并发性不是很大的话,使用她也是一个不错的选择。

2、MMapDirectory:使用内存映射IO的方式来操作索引,在性能上是非常优秀的,读写速度非常快,并发性支持一般,当然这种情况下仅仅局限于,你的索引的大小小于系统内存的时候,这才是一个好的选择,否则,使用不当,将常常会造成内存溢出的异常。

3、NIOFSDirectory:使用的是java nio的FileChannel的来操作索引的,读写速度快,对并发支持非常有些,因为它利用NIO的特性,避免了同步读取,所以在高并发的场景下,这个目录往往是最佳选择。

下面我们分析FSDirectory的另一个重要方法sysc()

  protected final Set<String> staleFiles = synchronizedSet(new HashSet<String>()); // Files written, but not yet sync'ed

  @Override
  public void sync(Collection<String> names) throws IOException {
    ensureOpen();
    Set<String> toSync = new HashSet<String>(names);//需要持久化的一些元数据标识
    toSync.retainAll(staleFiles);//此方法会与staleFiles里面的数据求交集

    for (String name : toSync)
      fsync(name);//把内存中或缓冲区的数据,强制写到磁盘上,确保数据不会流失

    staleFiles.removeAll(toSync);//在staleFiles中移除已经持久化到磁盘的数据,等待下一次的数据添加
  }

 protected void fsync(String name) throws IOException {
    File fullFile = new File(directory, name);
    boolean success = false;
    int retryCount = 0;
    IOException exc = null;
    while (!success && retryCount < 5) {
      retryCount++;
      RandomAccessFile file = null;
      try {
        try {
          file = new RandomAccessFile(fullFile, "rw");
          file.getFD().sync();//写入磁盘上
          success = true;
        } finally {
          if (file != null)
            file.close();
        }
      } catch (IOException ioe) {
        if (exc == null)
          exc = ioe;
        try {
          // Pause 5 msec
          Thread.sleep(5);
        } catch (InterruptedException ie) {
          throw new ThreadInterruptedException(ie);
        }
      }
    }
    if (!success)
      // Throw original exception
      throw exc;
  }

其实,sysc这个方法,是从Directory这个顶级父类,继承过来的,由FSDirectory这个类,对其进行了重写,这个方法的目的,就是定期根据某些条件,来讲我们内存或缓冲区的数据持久化到磁盘上,以确保我们已经索引的数据是非常安全的,不会因为一些以外的情况,如系统崩溃、或突然宕机、停电的情况下,对索引结构造成破坏或一些影响。

一个简单地工作流程是这样的,当我们进行添加操作是,文件目录通常会打开一个或几个特定的文件格式来存储我们的数据,比如索引正文的存储,向量的存储,位置增量的存储,不同的索引格式负责存储不同的内容,当一些数据添加完毕后,通过某些条件促发持久化操作,比如超出了设置的缓冲区大小,或者超出了默认的Doc数,或者我们调用了commit方法,这是lucene会调用sysc方法,来把已经添加的数据,存储到磁盘上,以确保数据的安全存储,当然这些工作,lucene底层已经给我们实现好了,我们并不需要显式的调用这个方法来完成数据的持久操作,就能绝大多情况下,安全可靠的完成存储,而这一切正是sysc发回的关键作用。


你可能感兴趣的:(Lucene)