欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-02 20:21:26
...
转载请务必注明,原创地址,谢谢配合!
http://qindongliang1922.iteye.com/blog/1990616


时间过的真快,又有半个月没更新lucene的文章了,散仙最近在工作上,稍微忙了一点,还学了一些其他的知识,不过这都不是借口,还是有点小懒啊,呵呵,今天晚上,就再写一篇吧,说实话,几天不写博客,心里挺憋的慌的,不知道你们是否有这种情况?学点技术,最好能整理一下到博客里,比较好,因为,我们的大脑就像一块内存,是有LRU功能的,定时持久化一下,还是很不错滴,希望在写博客的朋友们,也能够继续坚持下去,没有写过的,也可以尝试一下嘛!


好了,散仙每当写一篇博客时,总喜欢在开头部分唠叨几句心里话,大家别介意啦,下面开始进入正题,散仙在上篇文章中,介绍了lucene的存储根基Directory的一些原理,以及也给出了Directory家族的继承分布图,那么本篇呢,散仙将重点介绍下Directory的一个很重要的子类FSDirectory,为什么说此类非常重要呢?如果你是正在使用lucene的开发者,那么你就知道,我们经常使用的一行代码:
Directory directory=FSDirectory.open(new File(indexPath))

通过这行代码,我们可以获取一个Directory的子类文件存储目录,然后我们对索引的一些操作,都是以这个子类的文件目录为基础的,

下面我们从源码的角度来详细剖析下FSDirectory这个类的作用,在此之前,散仙先用一个表格来介绍下lucene存储索引的几种方式:


序号 方式
1 将索引存储在内存中
2 将索引通过JDBC存储在数据库中
3 将索引存储在一般的文件系统上,如Windows,Linux,Solaris
4 将索引存储在分布式文件系统上,如HDFS


上面的几种存储方式是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发挥的关键作用。


今天,散仙就分享到这里了,文章如有什么不当之处,欢迎指正交流,最后,感谢各位道友能坚持看完。

转载请务必注明,原创地址,谢谢配合!
http://qindongliang1922.iteye.com/blog/1990616