IndexDeletionPolicy以及多次commit

关于索引删除的策略IndexDeletionPolicy

IndexDeletionPolicy是一个接口,是有关在建立索引的过程中,对索引文件进行灵活地(删除)操作的一种自定义接口。可以在合适的时机进行删除操作,可以指定删除的时刻,完全可以根据自己的需要进行定制,但是,这可能会造成存储开销,但是相对于删除操作策略的灵活便利性,这也是比较值得的。

IndexDeletionPolicy接口的定义非常简单,而且为我们做了启发式的指引:

package org.apache.lucene.index;

import java.util.List;
import java.io.IOException;

// 该接口示例了两种删除索引文件的时机,即所谓的Points

public interface IndexDeletionPolicy {

// 一个IndexWriter索引器初始化的时候,可以删除一些时刻(提交点)提交的索引文件,这些提交点存放在一个List中,包括最早提交点和最近的提点之间的所有提交点,当然,删除他们是可选的,而且应当慎重,因为一旦删除便不可回滚。
public void onInit(List commits) throws IOException;

// 当新建的索引写入目录,并已经提交的时候,我们仍然可以删除指定List中一些时刻(提交点)提交的索引文件,当然,删除他们是可选的,而且应当慎重
public void onCommit(List commits) throws IOException;
}

 

通过对IndexDeletionPolicy接口的实现类进行学习,把握最基本的索引删除的动机和行为。通过IndexDeletionPolicy接口的实现类的类名就可以大概了解到它的索引删除策略:

1、KeepOnlyLastCommitDeletionPolicy策略

KeepOnlyLastCommitDeletionPolicy类在初始化一个IndexWriter的时候,在init方法中如果指定IndexDeletionPolicy deletionPolicy为null,则默认的索引删除策略为KeepOnlyLastCommitDeletionPolicy。

KeepOnlyLastCommitDeletionPolicy类的源代码非常容易理解,如下所示:

package org.apache.lucene.index;

import java.util.List;

public final class KeepOnlyLastCommitDeletionPolicy implements IndexDeletionPolicy {

//   初始化IndexWriter的时候,除了保留最近的一个提交点以外,删除所有提交点提交的索引文件
public void onInit(List commits) {
    // Note that commits.size() should normally be 1:
    onCommit(commits);
}

// 除了最近时刻的提交点保留以外,其余的全部删除
public void onCommit(List commits) {
    // Note that commits.size() should normally be 2 (if not
    // called by onInit above):
    int size = commits.size();
    for(int i=0;i<size-1;i++) {    // IndexCommitPoint也很重要,因为它涉及到了索引段,比较复杂,在后面系统学习
      ((IndexCommitPoint) commits.get(i)).delete();
    }
}
}

2、KeepNoneOnInitDeletionPolicy策略

初始化时删除所有提交点的索引段,最后提交的时候,保留最近提交点的索引段。

class KeepNoneOnInitDeletionPolicy implements IndexDeletionPolicy {
    int numOnInit;
    int numOnCommit;
    public void onInit(List commits) {
      verifyCommitOrder(commits);
      numOnInit++;
      // 初始化的时候,就删除所有的提交点
      Iterator it = commits.iterator();
      while(it.hasNext()) {
        ((IndexCommitPoint) it.next()).delete();
      }
    }
    public void onCommit(List commits) {

    //    验证每个提交点提交的索引文件(索引段)名称的正确性
      verifyCommitOrder(commits);
      int size = commits.size();
     // 除了最近时刻的提交点保留以外,其余的全部删除
      for(int i=0;i<size-1;i++) {
        ((IndexCommitPoint) commits.get(i)).delete();
      }
      numOnCommit++;
    }
}

3、KeepAllDeletionPolicy策略

保留所有提交点,不删除任何提交点的索引段。

class KeepAllDeletionPolicy implements IndexDeletionPolicy {
    int numOnInit;
    int numOnCommit;
    public void onInit(List commits) {
      verifyCommitOrder(commits);
      numOnInit++;
    }
    public void onCommit(List commits) {
      verifyCommitOrder(commits);
      numOnCommit++;
    }
}

4、KeepLastNDeletionPolicy策略

初始化时,不删除任何提交点;最后提交时,保留指定的最近的N个提交点。

class KeepLastNDeletionPolicy implements IndexDeletionPolicy {
    int numOnInit;
    int numOnCommit;
    int numToKeep;
    int numDelete;
    Set seen = new HashSet();    //   Set seen用于保证不重复删除某个提交点

    public KeepLastNDeletionPolicy(int numToKeep) {    //   初始化显式指定最近提交点的个数
      this.numToKeep = numToKeep;
    }

    public void onInit(List commits) {
      verifyCommitOrder(commits);
      numOnInit++;
      // false指定了不做任何删除
      doDeletes(commits, false);
    }

    public void onCommit(List commits) {
      verifyCommitOrder(commits);
      doDeletes(commits, true);    // 根据初始化的numToKeep,保留最近numToKeep个提交点
    }
    
    private void doDeletes(List commits, boolean isCommit) {
      if (isCommit) {
        String fileName = ((IndexCommitPoint) commits.get(commits.size()-1)).getSegmentsFileName();
        if (seen.contains(fileName)) {
          throw new RuntimeException("onCommit was called twice on the same commit point: " + fileName);
        }
        seen.add(fileName);    //   seen中加入的是已经删除的提交点
        numOnCommit++;
      }
      int size = commits.size();
      for(int i=0;i<size-numToKeep;i++) {    // 删除前size-numToKeep个提交点
        ((IndexCommitPoint) commits.get(i)).delete();
        numDelete++;
      }
    }
}

5、ExpirationTimeDeletionPolicy策略

如果某个提交的响应已经超过指定时间,则删除掉这个提交点。

class ExpirationTimeDeletionPolicy implements IndexDeletionPolicy {

    Directory dir;
    double expirationTimeSeconds;    // 指定提交超时时间
    int numDelete;

    public ExpirationTimeDeletionPolicy(Directory dir, double seconds) {
      this.dir = dir;
      this.expirationTimeSeconds = seconds;
    }

    public void onInit(List commits) throws IOException {
      verifyCommitOrder(commits);
      onCommit(commits);
    }

    public void onCommit(List commits) throws IOException {
      verifyCommitOrder(commits);

      IndexCommitPoint lastCommit = (IndexCommitPoint) commits.get(commits.size()-1);

      // 根据索引文件的最后提交时间
      double expireTime = dir.fileModified(lastCommit.getSegmentsFileName())/1000.0 - expirationTimeSeconds;

      Iterator it = commits.iterator();

      while(it.hasNext()) {
        IndexCommitPoint commit = (IndexCommitPoint) it.next();
        double modTime = dir.fileModified(commit.getSegmentsFileName())/1000.0;
        if (commit != lastCommit && modTime < expireTime) {
          commit.delete();
          numDelete += 1;
        }
      }
    }
}

OpenMode设置是和IndexDeletionPolicy配合使用的

只有注释掉索引删除策略配置,让其使用默认值,openmode的配置才会生效

IKAnalyzer analyzer = new IKAnalyzer(false);
		Directory directory = null;
//		File file = new File(indexPath);
//		Path path = file.toPath();
		Path path = Paths.get(indexPath);

		// 建立内存索引对象
		// directory = newRAMDirectory();

		// 建立物理文件索引
		directory = FSDirectory.open(path);
		// 读取原始文档内容,此处为文件夹路径,内可存放多个文本文档,每个文档代表一个doc
		File files = new File("/mnt/lucenetxt");

		//IndexWriterConfig和LiveIndexWriterConfig的联系和区别
		IndexWriterConfig config = new IndexWriterConfig(analyzer);
		//config.setCommitOnClose(false);
        config.setIndexDeletionPolicy(NoDeletionPolicy.INSTANCE);
		config.setMaxBufferedDocs(10);
		config.setUseCompoundFile(false);
		//创建一个IndexWriter对象,对于索引库进行写操作
		//在IndexWriter构造函数中创建写锁,进程级系统锁
		IndexWriter indexWriter = new IndexWriter(directory, config);
		// 遍历一个文件
		for (File f : files.listFiles()) {
			String fileName = f.getName();
			String fileContent = FileUtils.readFileToString(f);
			String filePath = f.getPath();
			long fileSize = FileUtils.sizeOf(f);


			// 创建一个Document对象
			Document document = new Document();
			// 向Document对象中添加域信息
			// 参数:1、域的名称;2、域的值;3、是否存储;
			Field contentField = new TextField("content", fileContent, Store.YES);
            Field contentField2 = new TextField("content2", fileContent, Store.YES);
			// 将域添加到document对象中
			document.add(contentField);
            document.add(contentField2);
			// 将信息写入到索引库中
			indexWriter.addDocument(document);

			
		}
        indexWriter.commit();

 

同一索引的多Commit

单个lucene索引可以保存多个commit,这是一个强大的特性,但往往容易被人们所忽视。每次commit都持有该commit被创建的时间点的过引索视图。
这个特点类似于ZFS和新兴的Btrfs这样的现代文件系统的快照和写克隆功能。事实上,Lucene是能够暴露多次commit状态,其内在的原理是:所有的索引分片和文件只会被写一次,正如ZFS和Btrfs上的块。

为了保存多次commit到你的索引中,只需要实现你自己的IndexDeletionPolicy,并将其传递给IndexWriter。Lucene正是通过这个类才知站定哪些commit是要被删除的:IndexWriter会在打开索引和任何一次完成commit的时候调用它。KeepOnlyLastCommitDeletionPolicy是默认的删除机制实现,它会删除掉除了最近一次commit以外的所有commit。如果采用NoDeletionPolicy,那么每一次commit都会保存。

你可以在commit的时候传送userData (Map<String,String>),用于记录关于本次commit的一些用户自定义信息(对Lucene不透明),然后使用IndexReader.listCommits方法获得索引的所有commit信息。一旦你找到了一次commit,你可以在其上找开一个IndexReader对commit点上的索引数据进行搜索。

你也可以基于之前的commit打开一个IndexWriter,回滚之后的所有变动。这有点类似于rollback方法,不同之处在于它允许你在多个commit间rollback,而不仅是回滚当前IndexWriter会话中所做的变动。

当你使用OpenMode.CREATE参数打开一个索引的时候,老的commit仍会保存。你也可使用OpenMode.CREATE,同时还在老的commit上用IndexReader进行搜索。这使得一些有趣的应用情影成为可能,例如,你可以在不影响当前任何打开的reader前提下,在各个commit间做过索引全量重建工作。

组合上这些有意思的事务特征,你可以完成一些很酷的工作:

  • 利用SnapShotDeletionPolicy或者PersistentSnapshotDeletionPolicy实现热备:
    these deletion policies make it trivial to take a "live" backup of the index without blocking ongoing changes with IndexWriter. 备份可以轻易的实现增量(只要复制新的文件,并去掉删除文件),而你可以减少IO消耗以减轻对于搜索的干扰。

  • 搜索不同版本的目录
    也许你在运行一个商业网站,却要辗转于不同版本的目录之间。在这种情况下,你就可以保存更老的commit,允许用户选择搜索哪个版本的目录。

  • 在同一个初始索引上进行多次索引实验:也许你想要在同一个大的索引上,运行一系列的性能测试实验,例如尝试不的RAM缓存大小或者merge因子。要想如此,你可以在运行完每次测试之后,不要关闭IndexWriter,而使用回滚方法快迅地恢得到初始状态,以备下次测试。

  • 强制merge所有的片段到单一片段中,但依然保存之前的多片段时候的commit。如此,你就可以做多个段vs单一段的性能比较实验。

  • 在NFS文件系统上做索引与搜索:
    由于NFS无法保证当前打开的文件不被删除,你必须使用IndexDeletionPolicy来保存每次提交,直至所有的reader都不再使用该commit(即,重新在一个更新的commit上打开)。一种简单的解决方案是基本于时间的,例如:不删除该commit,直到创建该commit15分钟之后,然后每5分钟重新打开reader一次。不然你在NFS上进行搜索时,将会遇到各种可怕的异常。

  • 分布式commit:
    如果你有其他的资源,需要有lucene索引发生变化的同时提交,你就可以使用二阶段提交API。这样的做法很简单,但容易在第二阶段发生失败; 例如lucene在其第二阶段完成了提交,而数据库在第二阶段中发生了一些错误、奔溃或者是断电,这时,你就可以通过在一个较早的commit上重新打开一个IndexWriter来rollback Lucene的这次commit。

  • 进行实验性的索引更新:也许你只想对索引的一个子集做一次re-indexing操作,但你又不确定这样的操作是否会成功。这种情况下,只要保存老的commit,如果操作失败,就执行rollback,如果成功则删除老的commit。

  • 基于时间的快照:也许你想能够更自由地回滚到1天前,一周前,一个月之前...的索引状态,就保可以根据这些时间点,保存这些commit。

注意:保存多个版本的commit必然会带来更多的磁盘空间消耗。然而,这些消耗往往会比较小,因为多个commit往往会共享一些索引片段,尤其是那些更大更早的片段

 public static void CreateIndex(String indexPath) throws IOException

    {
        // 实例化IKAnalyzer分词器
        IKAnalyzer analyzer = new IKAnalyzer(false);
        Directory directory = null;
//		File file = new File(indexPath);
//		Path path = file.toPath();
        Path path = Paths.get(indexPath);

        // 建立内存索引对象
        // directory = newRAMDirectory();

        // 建立物理文件索引
        directory = FSDirectory.open(path);
        // 读取原始文档内容,此处为文件夹路径,内可存放多个文本文档,每个文档代表一个doc
        File files = new File("/mnt/lucenetxt");

        //IndexWriterConfig和LiveIndexWriterConfig的联系和区别
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        //config.setCommitOnClose(false);
        config.setIndexDeletionPolicy(NoDeletionPolicy.INSTANCE);
        config.setMaxBufferedDocs(10);
        config.setUseCompoundFile(false);
        //创建一个IndexWriter对象,对于索引库进行写操作
        //在IndexWriter构造函数中创建写锁,进程级系统锁
        IndexWriter indexWriter = new IndexWriter(directory, config);
        // 遍历一个文件
        for (File f : files.listFiles()) {
            String fileName = f.getName();
            String fileContent = FileUtils.readFileToString(f);
            String filePath = f.getPath();
            long fileSize = FileUtils.sizeOf(f);


            // 创建一个Document对象
            Document document = new Document();
            // 向Document对象中添加域信息
            // 参数:1、域的名称;2、域的值;3、是否存储;
            Field contentField = new TextField("content", fileContent, Field.Store.YES);
            Field contentField2 = new TextField("content2", fileContent, Field.Store.YES);
            // 将域添加到document对象中
            document.add(contentField);
            document.add(contentField2);
            // 将信息写入到索引库中
            indexWriter.addDocument(document);


        }
        Map<String, String> map = new HashMap<>();
        map.put("1","1");
        indexWriter.setCommitData(map);
        indexWriter.commit();

        map.put("1","2");
        indexWriter.setCommitData(map);
        indexWriter.commit();

        map.put("1","3");
        indexWriter.setCommitData(map);
        indexWriter.commit();


    }

 

posted @ 2022-02-16 13:10  車輪の唄  阅读(14)  评论(0编辑  收藏  举报  来源