`
yupengcc
  • 浏览: 138704 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

用Lucene构建实时索引的文档更新问题

阅读更多

1、Lucene删除文档的几种方式

 

  • IndexReader.deleteDocument(int docID)是用 IndexReader 按文档号删除。  
  • IndexReader.deleteDocuments(Term  term)是用 IndexReader 删除包含此词(Term)的文档。  
  • IndexWriter.deleteDocuments(Term  term)是用 IndexWriter 删除包含此词(Term)的文档。  
  • IndexWriter.deleteDocuments(Term[]  terms)是用 IndexWriter 删除包含这些词(Term)的文档。  
  • IndexWriter.deleteDocuments(Query  query)是用 IndexWriter 删除能满足此查询(Query)的文档。  
  • IndexWriter.deleteDocuments(Query[] queries)是用 IndexWriter 删除能满足这些查询(Query)的文档。

删除文档既可以用reader进行删除,也可以用writer进行删除,不同的是,reader进行删除后,此reader马上能够生效,而用writer删除后,会被缓存,只有写入到索引文件中,当reader再次打开的时候,才能够看到。

2、Lucene文档更新的几个问题

 

2.1、使用IndexReader还是IndexWriter进行删除

既然IndexReader和IndexWriter都能够进行文档删除,那么到底是应该用哪个来进行删除呢?

本文的建议是,用IndexWriter来进行删除。

因为用IndexReader可能存在以下的问题:

(1) 当有一个IndexWriter打开的时候,IndexReader的删除操作是不能够进行的,否则会报LockObtainFailedException

(2) 当IndexReader被多个线程使用的时候,一个线程用其进行删除,会使得另一个线程看到的索引有所改变,使得另一个线程的结果带有不确定性。

(3) 对于更新操作,在Lucene中是先删除,再添加的,然而删除的被立刻看到的,而添加却不能够立刻看到,造成了数据的不一致性。

(4) 即便以上问题可以通过锁来解决,然而背后的操作影响到了搜索的速度,是我们不想看到的。

2.2、如何在内存中缓存文档的删除

在上一节中,为了能够做到实时性,我们使用内存中的索引,而硬盘上的索引则不经常打开,即便打开也在背后线程中打开。

而要删除的文档如果在硬盘索引中,如果不重新打开则看不到新的删除,则需要将删除的文档缓存到内存中。

那如何将缓存在内存中的文档删除在不重新打开IndexReader的情况下应用于硬盘上的索引呢?

在Lucene中,有一种IndexReader为FilterIndexReader,可以对一个IndexReader进行封装,我们可以实现一个自己的FilterIndexReader来过滤掉删除的文档。

一个例子如下:

 

public class MyFilterIndexReader extends FilterIndexReader {

  OpenBitSet dels;

  public MyFilterIndexReader(IndexReader in) {

    super(in);

    dels = new OpenBitSet(in.maxDoc());

  }

  public MyFilterIndexReader(IndexReader in, List<String> idToDelete) throws IOException {

    super(in);

    dels = new OpenBitSet(in.maxDoc());

    for(String id : idToDelete){

      TermDocs td = in.termDocs(new Term("id", id)); //如果能在内存中Cache从Lucene的ID到应用的ID的映射,Reader的生成将快得多。

      if(td.next()){

        dels.set(td.doc());

      }

    }

  }

  @Override

  public int numDocs() {

    return in.numDocs() - (int) dels.cardinality();

  }

  @Override

  public TermDocs termDocs(Term term) throws IOException {

    return new FilterTermDocs(in.termDocs(term)) {

      @Override

      public boolean next() throws IOException {

        boolean res;

        while ((res = super.next())) {

          if (!dels.get(doc())) {

            break;

          }

        }

        return res;

      }

    };

  }

  @Override

  public TermDocs termDocs() throws IOException {

    return new FilterTermDocs(in.termDocs()) {

      @Override

      public boolean next() throws IOException {

        boolean res;

        while ((res = super.next())) {

          if (!dels.get(doc())) {

            break;

          }

        }

        return res;

      }

    };

  }

}

 

2.3、文档更新的顺序性问题

Lucene的文档更新其实是删除旧的文档,然后添加新的文档。如上所述,删除的文档是缓存在内存中的,并通过FilterIndexReader应用于硬盘上的索引,然而新的文档也是以相同的id加入到索引中去的,这就需要保证缓存的删除不会将新的文档也过滤掉,将缓存的删除合并到索引中的时候不会将新的文档也删除掉。

Lucene的两次更新一定要后一次覆盖前一次,而不能让前一次覆盖后一次。

所以内存中已经硬盘中的多个索引是要被保持一个顺序的,哪个是老的索引,哪个是新的索引,缓存的删除自然是应该应用于所有比他老的索引的,而不应该应用于他自己以及比他新的索引。

3、具有更新功能的Lucene实时索引方案

3.1、初始化

首先假设我们硬盘上已经有一个索引FileSystemIndex,被事先打开的,其中包含文档1,2,3,4,5,6。

我们在内存中有一个索引MemoryIndex,新来的文档全部索引到内存索引中,并且是索引完IndexWriter就commit,IndexReader就重新打开,其中包含文档7,8。

绘图8

 

3.2、更新文档5

这时候来一个新的更新文档5, 需要首先将文档5删除,然后加入新的文档5。

需要做的事情是:

  • 首先在内存索引中删除文档5,当然没有文档5,删除无效。
  • 其次将对文档5的删除放入内存文档删除列表,并与硬盘的IndexReader组成FilterIndexReader
  • 最后,将新的文档5加入内存索引,这时候,用户可以看到的就是新的文档5了。
  • 将文档5放入删除列表以及将文档5提交到内存索引两者应该是一个原子操作,好在这两者都是比较块的。

注:此处对硬盘上的索引,也可以进行对文档5的删除,由于IndexReader没有重新打开,此删除是删不掉的,我们之所以没有这样做,是想保持此次更新要么全部在内存中,要么全部在硬盘中,而非删除部分已经应用到硬盘中,而新文档却在内存中,此时,如果系统crash,则新的文档5丢失了,而旧的文档5也已经在硬盘上被删除。我们将硬盘上对文档5的删除放到从内存索引向硬盘索引的合并过程。

更新文档5

如果再有一次对文档5的更新,则首先将内存索引中的文档5删除,添加新的文档5,然后将文档5加入删除列表,发现已经存在,则不必删除。

3.3、合并索引

然而经过一段时间,内存中的索引需要合并到硬盘上。

在合并的过程中,需要重新建立一个空的内存索引,用于合并阶段索引新的文档,而合并中的索引的IndexReader以及硬盘索引和删除列表所组成的FilterIndexReader仍然保持打开,对外提供服务,而合并阶段从后台进行。

后台的合并包括以下几步:

  • 将删除列表应用到硬盘索引中。
  • 将内存索引合并到硬盘索引中。
  • IndexWriter提交。

合并

3.4、合并的过程中更新文档5

在合并的过程中,如果还有更新那怎么办呢?

  • 首先将合并中索引的文档5删除,此删除不会影响合并,因为合并之前,合并中索引的IndexReader已经打开,索引合并中索引的文档5还是会合并到硬盘中去的。此删除影响的是此后的查询在合并中索引是看不到文档5的。
  • 然后将文档5的删除放入删除列表,并同合并中索引的删除列表,已经硬盘索引一起构成FilterIndexReader。
  • 将新的文档5添加到内存中索引。
  • 提交在合并中索引对文档5的删除,将文档5添加到删除列表,提交在内存索引中对文档5的添加三者应该是一个原子操作,好在三者也是很快的。

合并时更新

3.5、重新打开硬盘索引的IndexReader

当合并中索引合并到硬盘中的时候,是时候重新打开硬盘上的索引了,新打开的IndexReader是可以看到文档5的删除的。

如果这个时候有新的更新,也是添加到内存索引和删除列表的,比如我们更新文档6.

重新打开

3.6、替代IndexReader 

当IndexReader被重新打开后,则需要删除合并中的索引及其删除列表,将硬盘索引原来的IndexReader关闭,使用新的IndexReader。

替换IndexReader

 

分享到:
评论

相关推荐

    有关Lucene的问题(8):用Lucene构建实时索引的文档更新问题[整理].pdf

    在构建实时索引时,尤其是在处理文档的更新和删除时,需要理解Lucene提供的不同方法以及它们的适用场景。以下是对Lucene删除文档和更新文档的详细讨论。 1. Lucene删除文档的方式: - `IndexReader.deleteDocument...

    关于lucene建立数据库索引的更新说明

    1. **资料的准确性**:由于网络上的信息繁多且质量参差不齐,使用Lucene时要确保所参考的教程或文档是最新的,因为版本更新可能导致某些方法或功能的改变。例如,旧版本中可能使用的`Hits`类在新版本中已被废弃,应...

    基于lucene技术的增量索引

    本文将深入探讨如何利用Lucene实现增量索引,这是一种在数据库或文件系统更新时仅对新数据或变化数据进行索引的技术,以降低资源消耗并保持搜索性能。 **1. Lucene基础知识** Lucene首先需要理解的是它的核心概念,...

    Lucene之删除索引

    4. **批量删除**:如果需要删除大量文档,可以使用`IndexWriter.deleteDocuments(Query query)`,通过构建一个匹配需要删除的文档的Query对象,一次删除多条记录。这种方式更高效,因为它只需要处理一次位向量更新,...

    lucene 对 xml建立索引

    3. **索引文档元素** - 在SAX处理器中,对于每个元素的开始和结束事件,可以捕获元素的信息,并将其添加到Lucene的文档中。 - 可以使用`Field`类来表示文档中的一个字段,包括字段名和字段值。 - 特别注意:对于...

    lucene 索引小示例

    在创建索引时,Lucene会对文档进行分词,生成一系列的关键词(也称为术语或Token),然后构建倒排索引。倒排索引是一种数据结构,它将每个关键词与包含该关键词的文档位置相关联,使得我们可以迅速找到包含特定词汇...

    Lucene3.0创建索引

    它为开发者提供了构建搜索应用所需的所有基本工具,包括索引文档、执行查询、处理分析等。Lucene3.0是该系列的一个版本,在性能和稳定性方面都有所提升。 #### 二、创建索引的基本步骤 根据提供的描述,创建索引的...

    Lucene 索引的简单使用

    - **更新和删除**:使用IndexWriter可以更新已有文档,或通过ID删除文档。 - **多线程索引**:通过控制IndexWriter的并发设置,可以提高索引大量数据时的性能。 以上就是关于“Lucene索引的简单使用”的详细介绍,...

    Lucene.net中文帮助文档

    文档中的"例子"部分,通常会包含一系列的示例代码,这些示例能够帮助开发者快速理解如何使用Lucene.NET进行索引创建、查询执行和结果处理。通过实际的代码示例,你可以学习到如何初始化索引目录、添加文档、构建查询...

    Lucene索引器实例

    - 更新文档:Lucene不支持直接更新已存在的文档,而是通过删除旧文档并重新添加新文档的方式来实现。 - 删除文档:调用`IndexWriter`的`deleteDocuments`方法,传入匹配待删除文档的查询条件。 **5. 关闭索引器** ...

    lucene-4.6.0官方文档

    官方文档中还包含了许多错误处理和调试技巧,帮助开发者解决在实际使用过程中遇到的问题,如索引损坏、查询效率低下等。 总之,《Lucene 4.6.0官方文档》详尽地涵盖了Lucene的核心概念、API使用和最佳实践,对于...

    luke8用于查看lucene保存的索引库数据和文档数据

    Lucene的核心功能在于构建和管理全文索引。它将非结构化的文本数据(如网页、文档)转换为结构化的索引,以便进行快速的全文搜索。这一过程包括分词、建立倒排索引、存储字段值等步骤。Lucene支持多种高级特性,如...

    Lucene.NET全文索引搜索Demo项目

    5. **性能优化**:Lucene.NET提供了批量索引、缓存、内存管理等机制来提高性能,例如使用NRT(Near-Real-Time)搜索可以在不影响搜索速度的情况下实现近乎实时的索引更新。 6. **多语言支持**:除了使用...

    lucene索引优化多线程多目录创建索引

    - **文档更新**:如果存在实时更新的需求,需设计合适的更新策略,如实时索引或批量更新。 通过对“lucene_multiThreadIndex”压缩包的学习,你将掌握如何在Lucene中实现多线程索引,从而提高大型数据集的索引构建...

    lucene实现索引查询

    以下是关于使用Lucene实现索引查询的详细知识: ### 一、创建索引 创建索引是Lucene的核心过程,它涉及到以下步骤: 1. **定义索引目录**:首先,你需要指定一个目录来存储索引文件。这通常是一个文件夹,可以...

    Lucene技术文档doc

    Solr是基于Lucene构建的企业级搜索服务器,它提供了分布式搜索、多语言支持、XML/HTTP接口、实时添加更新等功能,使得部署和管理大规模搜索引擎变得更加容易。 **五、Lucene的版本发展** 从文件名lucena4.docx、...

    论文研究-基于MySQL和Lucene的反向索引系统的实时性能比较研究 .pdf

    本文研究的核心是比较MySQL和Lucene在构建倒排索引方面的实时性能。倒排索引是搜索引擎的核心数据结构,它影响着搜索引擎的使用场景和范围。开源搜索引擎工具包Lucene在批量索引时性能良好,但在提供实时索引时性能...

    Lucene-2.0学习文档

    `Indexer.java`中可能会包含对`Directory`(存储索引的容器)、`Analyzer`(负责文本分析,如分词)和`IndexWriter`(用于创建和更新索引)的使用。通过调用`IndexWriter`的`addDocument()`方法,我们可以将文档添加...

    深入 Lucene 索引机制

    《深入 Lucene 索引机制》这篇博文主要探讨了Lucene这个全文搜索引擎的核心索引原理,它在信息检索领域有着广泛的应用。Lucene是一个开源的Java库,它提供了高效、可扩展的文本搜索功能。以下是对Lucene索引机制的...

Global site tag (gtag.js) - Google Analytics