﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-huangfox-文章分类-搜索引擎技术</title><link>http://www.blogjava.net/huangfox/category/45692.html</link><description>韬光隐晦</description><language>zh-cn</language><lastBuildDate>Sat, 25 Sep 2010 22:42:26 GMT</lastBuildDate><pubDate>Sat, 25 Sep 2010 22:42:26 GMT</pubDate><ttl>60</ttl><item><title>Implementing a Lucene search engine【转】</title><link>http://www.blogjava.net/huangfox/articles/332874.html</link><dc:creator>fox009</dc:creator><author>fox009</author><pubDate>Sat, 25 Sep 2010 15:25:00 GMT</pubDate><guid>http://www.blogjava.net/huangfox/articles/332874.html</guid><wfw:comment>http://www.blogjava.net/huangfox/comments/332874.html</wfw:comment><comments>http://www.blogjava.net/huangfox/articles/332874.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huangfox/comments/commentRss/332874.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huangfox/services/trackbacks/332874.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 摘自：http://edn.embarcadero.com/article/38723By:&nbsp;John KasterAbstract: A guide to implementing Lucene as a cross-platform, cross-language search engine&nbsp;&nbsp;&nbsp;&nbsp;Imple...&nbsp;&nbsp;<a href='http://www.blogjava.net/huangfox/articles/332874.html'>阅读全文</a><img src ="http://www.blogjava.net/huangfox/aggbug/332874.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huangfox/" target="_blank">fox009</a> 2010-09-25 23:25 <a href="http://www.blogjava.net/huangfox/articles/332874.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>有关Lucene的问题(8)：用Lucene构建实时索引的文档更新问题【转】</title><link>http://www.blogjava.net/huangfox/articles/332845.html</link><dc:creator>fox009</dc:creator><author>fox009</author><pubDate>Sat, 25 Sep 2010 08:18:00 GMT</pubDate><guid>http://www.blogjava.net/huangfox/articles/332845.html</guid><wfw:comment>http://www.blogjava.net/huangfox/comments/332845.html</wfw:comment><comments>http://www.blogjava.net/huangfox/articles/332845.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huangfox/comments/commentRss/332845.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huangfox/services/trackbacks/332845.html</trackback:ping><description><![CDATA[<ul class="con">
    <p> 在有关Lucene的问题(7)，讨论了使用Lucene内存索引和硬盘索引构建实时索引的问题。</p>
    <p> 然而有的读者提到，如果涉及到文档的删除及更新，那么如何构建实时的索引呢？本节来讨论这个问题。</p>
    <p> 1、Lucene删除文档的几种方式</p>
    <p> IndexReader.deleteDocument(int docID)是用 IndexReader 按文档号删除。 </p>
    <p> IndexReader.deleteDocuments(Term term)是用 IndexReader 删除包含此词(Term)的文档。 </p>
    <p> IndexWriter.deleteDocuments(Term term)是用 IndexWriter 删除包含此词(Term)的文档。 </p>
    <p> IndexWriter.deleteDocuments(Term[] terms)是用 IndexWriter 删除包含这些词(Term)的文档。 </p>
    <p> IndexWriter.deleteDocuments(Query query)是用 IndexWriter 删除能满足此查询(Query)的文档。 </p>
    <p> IndexWriter.deleteDocuments(Query[] queries)是用 IndexWriter 删除能满足这些查询(Query)的文档。</p>
    <p> 删除文档既可以用reader进行删除，也可以用writer进行删除，不同的是，reader进行删除后，此reader马上能够生效，而用writer删除后，会被缓存，只有写入到索引文件中，当reader再次打开的时候，才能够看到。</p>
    <p> 2、Lucene文档更新的几个问题</p>
    <p> 2.1、使用IndexReader还是IndexWriter进行删除</p>
    <p> 既然IndexReader和IndexWriter都能够进行文档删除，那么到底是应该用哪个来进行删除呢？</p>
    <p> 本文的建议是，用IndexWriter来进行删除。</p>
    <p> 因为用IndexReader可能存在以下的问题：</p>
    <p> (1) 当有一个IndexWriter打开的时候，IndexReader的删除操作是不能够进行的，否则会报LockObtainFailedException</p>
    <p> (2) 当IndexReader被多个线程使用的时候，一个线程用其进行删除，会使得另一个线程看到的索引有所改变，使得另一个线程的结果带有不确定性。</p>
    <p> (3) 对于更新操作，在Lucene中是先删除，再添加的，然而删除的被立刻看到的，而添加却不能够立刻看到，造成了数据的不一致性。</p>
    <p> (4) 即便以上问题可以通过锁来解决，然而背后的操作影响到了搜索的速度，是我们不想看到的。</p>
    <p> 2.2、如何在内存中缓存文档的删除</p>
    <p> 在上一节中，为了能够做到实时性，我们使用内存中的索引，而硬盘上的索引则不经常打开，即便打开也在背后线程中打开。</p>
    <p> 而要删除的文档如果在硬盘索引中，如果不重新打开则看不到新的删除，则需要将删除的文档缓存到内存中。</p>
    <p> 那如何将缓存在内存中的文档删除在不重新打开IndexReader的情况下应用于硬盘上的索引呢？</p>
    <p> 在Lucene中，有一种IndexReader为FilterIndexReader，可以对一个IndexReader进行封装，我们可以实现一个自己的FilterIndexReader来过滤掉删除的文档。</p>
    <p> 一个例子如下：</p>
    <p> <code>public class MyFilterIndexReader extends FilterIndexReader {<br />
    OpenBitSet dels;<br />
    public MyFilterIndexReader(IndexReader in) {<br />
    super(in);<br />
    dels = new OpenBitSet(in.maxDoc());<br />
    }<br />
    public MyFilterIndexReader(IndexReader in, List&lt;String&gt; idToDelete) throws IOException {<br />
    super(in);<br />
    dels = new OpenBitSet(in.maxDoc());<br />
    for(String id : idToDelete){<br />
    TermDocs td = in.termDocs(new Term("id", id)); //如果能在内存中Cache从Lucene的ID到应用的ID的映射，Reader的生成将快得多。<br />
    if(td.next()){<br />
    dels.set(td.doc());<br />
    }<br />
    }<br />
    }<br />
    @Override<br />
    public int numDocs() {<br />
    return in.numDocs() - (int) dels.cardinality();<br />
    }<br />
    @Override<br />
    public TermDocs termDocs(Term term) throws IOException {<br />
    return new FilterTermDocs(in.termDocs(term)) {<br />
    @Override<br />
    public boolean next() throws IOException {<br />
    boolean res;<br />
    while ((res = super.next())) {<br />
    if (!dels.get(doc())) {<br />
    break;<br />
    }<br />
    }<br />
    return res;<br />
    }<br />
    };<br />
    }<br />
    @Override<br />
    public TermDocs termDocs() throws IOException {<br />
    return new FilterTermDocs(in.termDocs()) {<br />
    @Override<br />
    public boolean next() throws IOException {<br />
    boolean res;<br />
    while ((res = super.next())) {<br />
    if (!dels.get(doc())) {<br />
    break;<br />
    }<br />
    }<br />
    return res;<br />
    }<br />
    };<br />
    }<br />
    }</code> </p>
    <p> 2.3、文档更新的顺序性问题</p>
    <p>
    Lucene
    的文档更新其实是删除旧的文档，然后添加新的文档。如上所述，删除的文档是缓存在内存中的，并通过FilterIndexReader应用于硬盘上的索
    引，然而新的文档也是以相同的id加入到索引中去的，这就需要保证缓存的删除不会将新的文档也过滤掉，将缓存的删除合并到索引中的时候不会将新的文档也删
    除掉。</p>
    <p> Lucene的两次更新一定要后一次覆盖前一次，而不能让前一次覆盖后一次。</p>
    <p> 所以内存中已经硬盘中的多个索引是要被保持一个顺序的，哪个是老的索引，哪个是新的索引，缓存的删除自然是应该应用于所有比他老的索引的，而不应该应用于他自己以及比他新的索引。</p>
    <p> 3、具有更新功能的Lucene实时索引方案3.1、初始化</p>
    <p> 首先假设我们硬盘上已经有一个索引FileSystemIndex，被事先打开的，其中包含文档1,2,3,4,5,6。</p>
    <p> 我们在内存中有一个索引MemoryIndex，新来的文档全部索引到内存索引中，并且是索引完IndexWriter就commit，IndexReader就重新打开，其中包含文档7,8。</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/e7d72f36_srcsky_144d5eae.JPEG" alt="" width="283" height="373" /> </p>
    <p> 3.2、更新文档5</p>
    <p> 这时候来一个新的更新文档5, 需要首先将文档5删除，然后加入新的文档5。</p>
    <p> 需要做的事情是：</p>
    <p> 首先在内存索引中删除文档5，当然没有文档5，删除无效。</p>
    <p> 其次将对文档5的删除放入内存文档删除列表，并与硬盘的IndexReader组成FilterIndexReader</p>
    <p> 最后，将新的文档5加入内存索引，这时候，用户可以看到的就是新的文档5了。</p>
    <p> 将文档5放入删除列表以及将文档5提交到内存索引两者应该是一个原子操作，好在这两者都是比较块的。</p>
    <p>
    注：此处对硬盘上的索引，也可以进行对文档5的删除，由于IndexReader没有重新打开，此删除是删不掉的，我们之所以没有这样做，是想保持此次更
    新要么全部在内存中，要么全部在硬盘中，而非删除部分已经应用到硬盘中，而新文档却在内存中，此时，如果系统crash，则新的文档5丢失了，而旧的文档
    5也已经在硬盘上被删除。我们将硬盘上对文档5的删除放到从内存索引向硬盘索引的合并过程。</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/f6157647_srcsky_82687cf6.JPEG" alt="" width="447" height="400" /> </p>
    <p> 如果再有一次对文档5的更新，则首先将内存索引中的文档5删除，添加新的文档5，然后将文档5加入删除列表，发现已经存在，则不必删除。</p>
    <p> 3.3、合并索引</p>
    <p> 然而经过一段时间，内存中的索引需要合并到硬盘上。</p>
    <p> 在合并的过程中，需要重新建立一个空的内存索引，用于合并阶段索引新的文档，而合并中的索引的IndexReader以及硬盘索引和删除列表所组成的FilterIndexReader仍然保持打开，对外提供服务，而合并阶段从后台进行。</p>
    <p> 后台的合并包括以下几步：</p>
    <p> 将删除列表应用到硬盘索引中。</p>
    <p> 将内存索引合并到硬盘索引中。</p>
    <p> IndexWriter提交。</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/f102f640_srcsky_bce3c10a.JPEG" alt="" width="550" height="448" /> </p>
    <p> 查看原图（大图）</p>
    <p> 3.4、合并的过程中更新文档5</p>
    <p> 在合并的过程中，如果还有更新那怎么办呢？</p>
    <p> 首先将合并中索引的文档5删除，此删除不会影响合并，因为合并之前，合并中索引的IndexReader已经打开，索引合并中索引的文档5还是会合并到硬盘中去的。此删除影响的是此后的查询在合并中索引是看不到文档5的。</p>
    <p> 然后将文档5的删除放入删除列表，并同合并中索引的删除列表，已经硬盘索引一起构成FilterIndexReader。</p>
    <p> 将新的文档5添加到内存中索引。</p>
    <p> 提交在合并中索引对文档5的删除，将文档5添加到删除列表，提交在内存索引中对文档5的添加三者应该是一个原子操作，好在三者也是很快的。</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/c537340f_srcsky_98ef4658.JPEG" alt="" width="550" height="392" /> </p>
    <p> 查看原图（大图）</p>
    <p> 3.5、重新打开硬盘索引的IndexReader</p>
    <p> 当合并中索引合并到硬盘中的时候，是时候重新打开硬盘上的索引了，新打开的IndexReader是可以看到文档5的删除的。</p>
    <p> 如果这个时候有新的更新，也是添加到内存索引和删除列表的，比如我们更新文档6.</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/c04ef332_srcsky_42ff233c.JPEG" alt="" width="550" height="406" /> </p>
    <p> 查看原图（大图）</p>
    <p> 3.6、替代IndexReader </p>
    <p> 当IndexReader被重新打开后，则需要删除合并中的索引及其删除列表，将硬盘索引原来的IndexReader关闭，使用新的IndexReader。</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/065c6960_srcsky_04244b19.JPEG" alt="" width="550" height="349" /> </p>
    <p> 查看原图（大图）</p>
</ul>
<img src ="http://www.blogjava.net/huangfox/aggbug/332845.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huangfox/" target="_blank">fox009</a> 2010-09-25 16:18 <a href="http://www.blogjava.net/huangfox/articles/332845.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>有关Lucene的问题(7)：用Lucene构建实时的索引【转】</title><link>http://www.blogjava.net/huangfox/articles/332844.html</link><dc:creator>fox009</dc:creator><author>fox009</author><pubDate>Sat, 25 Sep 2010 08:15:00 GMT</pubDate><guid>http://www.blogjava.net/huangfox/articles/332844.html</guid><wfw:comment>http://www.blogjava.net/huangfox/comments/332844.html</wfw:comment><comments>http://www.blogjava.net/huangfox/articles/332844.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huangfox/comments/commentRss/332844.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huangfox/services/trackbacks/332844.html</trackback:ping><description><![CDATA[<ul class="con">
    <p>
    由于前一章所述的Lucene的事务性，使得Lucene可以增量的添加一个段，我们知道，倒排索引是有一定的格式的，而这个格式一旦写入是非常难以改变
    的，那么如何能够增量建索引呢？Lucene使用段这个概念解决了这个问题，对于每个已经生成的段，其倒排索引结构不会再改变，而增量添加的文档添加到新
    的段中，段之间在一定的时刻进行合并，从而形成新的倒排索引结构。</p>
    <p>
    然而也正因为Lucene的事务性，使得Lucene的索引不够实时，如果想Lucene实时，则必须新添加的文档后IndexWriter需要
    commit，在搜索的时候IndexReader需要重新的打开，然而当索引在硬盘上的时候，尤其是索引非常大的时候，IndexWriter的
    commit操作和IndexReader的open操作都是非常慢的，根本达不到实时性的需要。</p>
    <p> 好在Lucene提供了RAMDirectory，也即内存中的索引，能够很快的commit和open，然而又存在如果索引很大，内存中不能够放下的问题。</p>
    <p> 所以要构建实时的索引，就需要内存中的索引RAMDirectory和硬盘上的索引FSDirectory相互配合来解决问题。</p>
    <p> 1、初始化阶段</p>
    <p> 首先假设我们硬盘上已经有一个索引FileSystemIndex，由于IndexReader打开此索引非常的慢，因而其是需要事先打开的，并且不会时常的重新打开。</p>
    <p> 我们在内存中有一个索引MemoryIndex，新来的文档全部索引到内存索引中，并且是索引完IndexWriter就commit，IndexReader就重新打开，这两个操作时非常快的。</p>
    <p> 如下图，则此时新索引的文档全部能被用户看到，达到实时的目的。</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/3325779b_srcsky_9749e165.JPEG" alt="有关Lucene的问题(7)：用Lucene构建实时的索引" width="443" height="360" /> </p>
    <p> 2、合并索引阶段</p>
    <p> 然而经过一段时间，内存中的索引会比较大了，如果不合并到硬盘上，则可能造成内存不够用，则需要进行合并的过程。</p>
    <p> 当然在合并的过程中，我们依然想让我们的搜索是实时的，这是就需要一个过渡的索引，我们称为MergingIndex。</p>
    <p> 一旦内存索引达到一定的程度，则我们重新建立一个空的内存索引，用于合并阶段索引新的文档，然后将原来的内存索引称为合并中索引，并启动一个后台线程进行合并的操作。</p>
    <p>
    在合并的过程中，如果有查询过来，则需要三个IndexReader，一个是内存索引的IndexReader打开，这个过程是很快的，一个是合并中索引
    的
    IndexReader打开，这个过程也是很快的，一个是已经打开的硬盘索引的IndexReader，无需重新打开。这三个IndexReader可以
    覆盖所有的文档，唯一有可能重复的是，硬盘索引中已经有一些从合并中索引合并过去的文档了，然而不用担心，根据Lucene的事务性，在硬盘索引的
    IndexReader没有重新打开的情况下，背后的合并操作它是看不到的，因而这三个IndexReader所看到的文档应该是既不少也不多。合并使用
    IndexWriter(硬盘索引).addIndexes(IndexReader(合并中索引))，合并结束后Commit。</p>
    <p> 如下图：</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/4a3a3e22_srcsky_f3da6d2c.JPEG" alt="有关Lucene的问题(7)：用Lucene构建实时的索引" width="550" height="425" /> </p>
    <p> 查看原图（大图）</p>
    <p> 3、重新打开硬盘索引的IndexReader</p>
    <p>
    当合并结束后，是应该重新打开硬盘索引的时候了，然而这是一个可能比较慢的过程，在此过程中，我们仍然想保持实时性，因而在此过程中，合并中的索引不能丢
    弃，硬盘索引的IndexReader也不要动，而是为硬盘索引打开一个临时的IndexReader，在打开的过程中，如果有搜索进来，返回的仍然是上
    述的三个IndexReader，仍能够不多不少的看到所有的文档，而将要打开的临时的IndexReader将能看到合并中索引和原来的硬盘索引所有的
    文档，此IndexReader并不返回给客户。如下图：</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/a64c1dee_srcsky_4d51c452.JPEG" alt="有关Lucene的问题(7)：用Lucene构建实时的索引" width="550" height="352" /> </p>
    <p> 查看原图（大图）</p>
    <p> 4、替代IndexReader</p>
    <p> 当临时的IndexReader被打开的时候，其看到的是合并中索引的IndexReader和硬盘索引原来的IndexReader之和，下面要做的是:</p>
    <p> (1) 关闭合并中索引的IndexReader</p>
    <p> (2) 抛弃合并中索引</p>
    <p> (3) 用临时的IndexReader替换硬盘索引原来的IndexReader</p>
    <p> (4) 关闭硬盘索引原来的IndexReader。</p>
    <p> 上面说的这几个操作必须是原子性的，如果做了(2)但没有做(3)，如果来一个搜索，则将少看到一部分数据，如果做了(3)没有做(2)则，多看到一部分数据。</p>
    <p>
    所以在进行上述四步操作的时候，需要加一个锁，如果这个时候有搜索进来的时候，或者在完全没有做的时候得到所有的IndexReader，或者在完全做好
    的时候得到所有的IndexReader，这时此搜索可能被block，但是没有关系，这四步是非常快的，丝毫不影响替代性。</p>
    <p> 如下图：</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/1067833d_srcsky_4b609109.JPEG" alt="有关Lucene的问题(7)：用Lucene构建实时的索引" width="550" height="296" /> </p>
    <p> 查看原图（大图）</p>
    <p> 经过这几个过程，又达到了第一步的状态，则进行下一个合并的过程。</p>
    <p> 5、多个索引</p>
    <p>
    有一点需要注意的是，在上述的合并过程中，新添加的文档是始终添加到内存索引中的，如果存在如下的情况，索引速度实在太快，在合并过程没有完成的时候，内
    存索引又满了，或者硬盘上的索引实在太大，合并和重新打开要花费太长的时间，使得内存索引以及满的情况下，还没有合并完成。</p>
    <p> 为了处理这种情况，我们可以拥有多个合并中的索引，多个硬盘上的索引，如下图：</p>
    <p> <img src="http://static.srcsky.com/fileuploader/10002/0/755/c5fbc4fd_srcsky_14aa858d.JPEG" alt="有关Lucene的问题(7)：用Lucene构建实时的索引" width="550" height="555" /> </p>
    <p> 查看原图（大图）</p>
    <p> 新添加的文档永远是进入内存索引</p>
    <p> 当内存索引到达一定的大小的时候，将其加入合并中索引链表</p>
    <p>
    有一个后台线程，每隔一定的时刻，将合并中索引写入一个新的硬盘索引中取。这样可以避免由于硬盘索引过大而合并较慢的情况。硬盘索引的
    IndexReader也是写完并重新打开后才替换合并中索引的IndexReader，新的硬盘索引也可保证打开的过程不会花费太长时间。</p>
    <p> 这样会造成硬盘索引很多，所以，每隔一定的时刻，将硬盘索引合并成一个大的索引。也是合并完成后方才替换IndexReader</p>
    <p>
    大家可能会发现，此合并的过程和Lucene的段的合并很相似。然而Lucene的一个函数IndexReader.reopen一直是没有实现的，也即
    我们不能选择哪个段是在内存中的，可以被打开，哪些是硬盘中的，需要在后台打开然后进行替换，而IndexReader.open是会打开所有的内存中的
    和硬盘上的索引，因而会很慢，从而降低了实时性。</p>
</ul>
<img src ="http://www.blogjava.net/huangfox/aggbug/332844.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huangfox/" target="_blank">fox009</a> 2010-09-25 16:15 <a href="http://www.blogjava.net/huangfox/articles/332844.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>有关Lucene的问题(6)：Lucene的事务性【转】</title><link>http://www.blogjava.net/huangfox/articles/332842.html</link><dc:creator>fox009</dc:creator><author>fox009</author><pubDate>Sat, 25 Sep 2010 08:02:00 GMT</pubDate><guid>http://www.blogjava.net/huangfox/articles/332842.html</guid><wfw:comment>http://www.blogjava.net/huangfox/comments/332842.html</wfw:comment><comments>http://www.blogjava.net/huangfox/articles/332842.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huangfox/comments/commentRss/332842.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huangfox/services/trackbacks/332842.html</trackback:ping><description><![CDATA[<ul class="con">
    <p> 所谓事务性，本多指数据库的属性，包括ACID四个基本要素：原子性(Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）。</p>
    <p> 我们这里主要讨论隔离性，Lucene的IndexReader和IndexWriter具有隔离性。</p>
    <p> <span style="color: red;">当IndexReader.open打开一个索引的时候，相对于给当前索引进行了一次snapshot，此后的任何修改都不会被看到。</span></p>
    <p> <span style="color: red;">仅当IndexReader.open打开一个索引后，才有可能看到从上次打开后对索引的修改。</span></p>
    <p> <span style="color: red;">当IndexWriter没有调用Commit的时候，其修改的内容是不能够被看到的，<strong>哪怕IndexReader被重新打开。</strong></span></p>
    <p> <strong><span style="color: red;">欲使最新的修改被看到，一方面IndexWriter需要commit，一方面IndexReader重新打开。</span></strong></p>
    <p> 下面我们举几个例子来说明上述隔离性：</p>
    <p> (1) 首先做准备，索引十篇文档</p>
    <p> <code>File indexDir = new File("TestIsolation/index");<br />
    IndexWriter
    writer = new IndexWriter(FSDirectory.open(indexDir), new
    StandardAnalyzer(Version.LUCENE_CURRENT), true,
    IndexWriter.MaxFieldLength.LIMITED);<br />
    for(int i =0; i &lt; 10; i++){<br />
    indexDocs(writer);<br />
    }<br />
    writer.close(); </code> </p>
    <p> (2) 然后再索引十篇文档，并不commit</p>
    <p> <code>writer
    = new IndexWriter(FSDirectory.open(indexDir), new
    StandardAnalyzer(Version.LUCENE_CURRENT),
    IndexWriter.MaxFieldLength.LIMITED);<br />
    for(int i =0; i &lt; 10; i++){<br />
    indexDocs(writer);<br />
    }</code> </p>
    <p> (3) 打开一个IndexReader，但是由于IndexWriter没有commit，所以仍然仅看到十篇文档。</p>
    <p> <code>IndexReader reader = IndexReader.open(FSDirectory.open(indexDir));<br />
    IndexSearcher searcher = new IndexSearcher(reader);<br />
    TopDocs docs = searcher.search(new TermQuery(new Term("contents","hello")), 50);<br />
    System.out.println(docs.totalHits);</code> </p>
    <p> (4) IndexWriter进行提交commit</p>
    <p> writer.commit();</p>
    <p> (5) 不重新打开IndexReader，进行搜索，仍然仅看到十篇文档。</p>
    <p> <code>docs = searcher.search(new TermQuery(new Term("contents","hello")), 50);<br />
    System.out.println(docs.totalHits); </code> </p>
    <p> (6) IndexReader重新打开，则可以看到二十篇文档。</p>
    <p> <code>reader = IndexReader.open(FSDirectory.open(indexDir));<br />
    searcher = new IndexSearcher(reader);<br />
    docs = searcher.search(new TermQuery(new Term("contents","hello")), 50);<br />
    System.out.println(docs.totalHits);</code> </p>
</ul>
<img src ="http://www.blogjava.net/huangfox/aggbug/332842.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huangfox/" target="_blank">fox009</a> 2010-09-25 16:02 <a href="http://www.blogjava.net/huangfox/articles/332842.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>避免lucene queryparser中文分词的缺陷【转】</title><link>http://www.blogjava.net/huangfox/articles/332799.html</link><dc:creator>fox009</dc:creator><author>fox009</author><pubDate>Sat, 25 Sep 2010 02:35:00 GMT</pubDate><guid>http://www.blogjava.net/huangfox/articles/332799.html</guid><wfw:comment>http://www.blogjava.net/huangfox/comments/332799.html</wfw:comment><comments>http://www.blogjava.net/huangfox/articles/332799.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/huangfox/comments/commentRss/332799.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huangfox/services/trackbacks/332799.html</trackback:ping><description><![CDATA[<div class="excerpt">
<p>很多人在使用lucene时会使用其提供的queryparser分析query。不过，lucene的queryparser从一开始到现在都没有充分考虑中文等语言的特点，使得查询中文会出现让人不可理解的查不到结果的情况。这个bug就是<a href="https://issues.apache.org/jira/browse/LUCENE-2458" title="https://issues.apache.org/jira/browse/LUCENE-2458">LUCENE-2458</a>。</p>
<p>这
个问题简单说来就是，对于一个连续的中文query，queryparser将Analyzer返回的Term序列构成了PhraseQuery（也有可
能是MultiPhraseQuery），而PhraseQuery默认的匹配规则是要求Term序列在索引的文档中完全顺序匹配。这对于英文查询来说是
可以接受的，因为queryparser在分析query时，首先通过AND、OR、NOT将query进行切分（这可以理解为queryparser
的第一层分析，这样切分后构成的TOP
Query就是BooleanQuery），然后将切分后的subquery交由Analyzer分析（当然这要求是满足FieldQuery的情况，否
则也可能是RangeQuery、WildcardQuery等），因为英文单词之间以空格分割，相当于OR查询，所以英文中的subquery就可以理
解是个短语（比如由多个连字符连接的短语，或者是英文和数字接合的短语，在lucene查询语法中，显示的双引号之间的内容认为是短语）。但对中文来说，
如果将subquery分析成PhraseQuery，就很成问题。比如subquery是&#8221;诺基亚N97&#8220;，如果构成PhraseQuery，则要求索
引的文档中必须存在&#8221;诺基亚N97&#8220;，如果&#8221;诺基亚&#8220;和&#8221;N97&#8220;中间有其他词，就不算匹配。对于这个例子，是可以调整PhraseQuery的
slop参数来变相解决，但这种情况，使用AND
BooleanQuery更合适，使用BooleanQuery在对文档打分上也要比PhraseQuery好很多。而对于query分词结果，也存在一
些TermQuery之间是OR的情况，使用PhraseQuery显然也不合适。</p>
<p>如LUCENE-2458提到，这个bug会在3.1和
4中被修复，修复方法是，只有显示通过双引号括起来的subquery才生成
PhraseQuery，否则可以派生子类来自定义处理。就目前使用来说，如果你使用IK做Analyzer，那么它提供的IKQueryParser是
很好的替代方案，它构造的就是由AND和OR联合的BooleanQuery。但因为BooleanQuery没有考虑各个Term在文档中的位置关系，
一味的根据词频计算得分，检索效果有时也不是很好。不知道大家是怎么处理的？我有想到去扩展它的Query和Scorer，不过看起来有些麻烦，暂且还没
精力投入上去。</p>
</div>
<img src ="http://www.blogjava.net/huangfox/aggbug/332799.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huangfox/" target="_blank">fox009</a> 2010-09-25 10:35 <a href="http://www.blogjava.net/huangfox/articles/332799.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>影响Lucene索引速度原因及提高速度技巧 【转】</title><link>http://www.blogjava.net/huangfox/articles/332798.html</link><dc:creator>fox009</dc:creator><author>fox009</author><pubDate>Sat, 25 Sep 2010 02:33:00 GMT</pubDate><guid>http://www.blogjava.net/huangfox/articles/332798.html</guid><wfw:comment>http://www.blogjava.net/huangfox/comments/332798.html</wfw:comment><comments>http://www.blogjava.net/huangfox/articles/332798.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huangfox/comments/commentRss/332798.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huangfox/services/trackbacks/332798.html</trackback:ping><description><![CDATA[<p style="text-indent: 2em;">【赛迪网-IT技术讯】先来看下影响索引的主要因素：
</p>
<p style="text-indent: 2em;">MaxMergeDocs
</p>
<p style="text-indent: 2em;">该参数决定写入内存索引文档个数，到达该数目后就把该内存索引写入硬盘，生成一个新的索引segment文件。
</p>
<p style="text-indent: 2em;">所以该参数也就是一个内存buffer，一般来说越大索引速度越快。
</p>
<p style="text-indent: 2em;">MaxBufferedDocs这个参数默认是disabled的，因为Lucene中还用另外一个参数（RAMBufferSizeMB）控制这个bufffer的索引文档个数。
</p>
<p style="text-indent: 2em;">其实MaxBufferedDocs和RAMBufferSizeMB这两个参数是可以一起使用的，一起使用时只要有一个触发条件满足就写入硬盘，生成一个新的索引segment文件。
</p>
<p style="text-indent: 2em;">RAMBufferSizeMB
</p>
<p style="text-indent: 2em;">控制用于buffer索引文档的内存上限，如果buffer的索引文档个数到达该上限就写入硬盘。当然，一般来说也只越大索引速度越快。
</p>
<p style="text-indent: 2em;">当我们对文档大小不太确定时，这个参数就相当有用，不至于outofmemory error。
</p>
<p style="text-indent: 2em;">MergeFactor
</p>
<p style="text-indent: 2em;">这个参数是用于子索引（Segment）合并的。
</p>
<p style="text-indent: 2em;">Lucene中索引总体上是这样进行，索引现写到内存，触发一定限制条件后写入硬
盘，生成一个独立的子索引－lucene中叫Segment。一般来说这些子索引需要合并成一个索引，也就是optimize()，否则会影响检索速度，
而且也可能导致open too many files。 </p>
<p style="text-indent: 2em;">MergeFactor 这个参数就是控制当硬盘中有多少个子索引segments，我们就需要现把这些索引合并冲一个稍微大些的索引了。
</p>
<p style="text-indent: 2em;">MergeFactor这个不能设置太大，特别是当MaxBufferedDocs比较小时（segment 越多），否则会导致open too many files错误，甚至导致虚拟机外面出错。
</p>
<p style="text-indent: 2em;">Note: Lucene
中默认索引合并机制并不是两两合并，好像是多个segment
合并成最终的一个大索引，所以MergeFactor越大耗费内存越多，索引速度也会快些，但我的感觉太大譬如300，最后合并的时候还是很满。
Batch indexing 应 MergeFactor&gt;10 </p>
<p style="text-indent: 2em;"><strong>加快索引的一些技巧</strong>
</p>
<p style="text-indent: 2em;">&#8226; 确认你在使用最新的Lucene版本。
</p>
<p style="text-indent: 2em;">&#8226; 尽量使用本地文件系统
</p>
<p style="text-indent: 2em;">远程文件系统一般来说都会降低索引速度。如果索引必须分布在远程服务器，请尝试先在本地生成索引，然后分发到远程服务器上。
</p>
<p style="text-indent: 2em;">&#8226; 使用更快的硬件设备，特别是更快的IO设备
</p>
<p style="text-indent: 2em;">&#8226; 在索引期间复用单一的IndexWriter实例
</p>
<p style="text-indent: 2em;">&#8226; 使用按照内存消耗Flush代替根据文档数量Flush
</p>
<p style="text-indent: 2em;">在Lucene
2.2之前的版本，可以在每次添加文档后调用ramSizeInBytes方法，当索引消耗过多的内存时，然后在调用flush()方法。这样做在索引大
量小文档或者文档大小不定的情况下尤为有效。你必须先把maxBufferedDocs参数设置足够大，以防止writer基于文档数量flush。但是
注意，别把这个值设置的太大，否则你将遭遇Lucene-845号BUG。不过这个BUG已经在2.3版本中得到解决。 </p>
<p style="text-indent: 2em;">在Lucene2.3之后的版本。IndexWriter可以自动的根据内存消
耗调用flush()。你可以通过writer.setRAMBufferSizeMB()来设置缓存大小。当你打算按照内存大小flush后，确保没有
在别的地方设置MaxBufferedDocs值。否则flush条件将变的不确定（谁先符合条件就按照谁）。 </p>
<p style="text-indent: 2em;">&#8226; 在你能承受的范围内使用更多的内存
</p>
<p style="text-indent: 2em;">在flush前使用更多的内存意味着Lucene将在索引时生成更大的
segment，也意味着合并次数也随之减少。在Lucene-843中测试，大概48MB内存可能是一个比较合适的值。但是，你的程序可能会是另外一个
值。这跟不同的机器也有一定的关系，请自己多加测试，选择一个权衡值。 </p>
<p style="text-indent: 2em;">&#8226; 关闭复合文件格式
</p>
<p style="text-indent: 2em;">调用setUseCompoundFile(false)可以关闭复合文件选项。生
成复合文件将消耗更多的时间（经过Lucene-888测试，大概会增加7%-33%的时间）。但是请注意，这样做将大大的增加搜索和索引使用的文件句柄
的数量。如果合并因子也很大的话，你可能会出现用光文件句柄的情况。 </p>
<p style="text-indent: 2em;">&#8226; 重用Document和Field实例
</p>
<p style="text-indent: 2em;">在lucene 2.3中，新增了一个叫setValue的方法，可以允许你改变字段的值。这样的好处是你可以在整个索引进程中复用一个Filed实例。这将极大的减少GC负担。
</p>
<p style="text-indent: 2em;">最好创建一个单一的Document实例，然后添加你想要的字段到文档中。同时复用添加到文档的Field实例，通用调用相应的SetValue方法改变相应的字段的值。然后重新将Document添加到索引中。
</p>
<p style="text-indent: 2em;">注意：你不能在一个文档中多个字段共用一个Field实例，在文档添加到索引之前，Field的值都不应该改变。也就是说如果你有3个字段，你必须创建3个Field实例，然后再之后的Document添加过程中复用它们。
</p>
<p style="text-indent: 2em;">&#8226; 在你的分析器Analyzer中使用一个单一的Token实例
</p>
<p style="text-indent: 2em;">在分析器中共享一个单一的token实例也将缓解GC的压力。
</p>
<p style="text-indent: 2em;">&#8226; 在Token中使用char[]接口来代替String接口来表示数据
</p>
<p style="text-indent: 2em;">在Lucene 2.3中，Token可以使用char数组来表示他的数据。这样可以避免构建字符串以及GC回收字符串的消耗。通过配合使用单一Token实例和使用char[]接口你可以避免创建新的对象。
</p>
<p style="text-indent: 2em;">&#8226; 设置autoCommit为false
</p>
<p style="text-indent: 2em;">在Lucene
2.3中对拥有存储字段和Term向量的文档进行了大量的优化，以节省大索引合并的时间。你可以将单一复用的IndexWriter实例的
autoCommit设置为false来见证这些优化带来的好处。注意这样做将导致searcher在IndexWriter关闭之前不会看到任何索引的
更新。如果你认为这个对你很重要，你可以继续将autoCommit设置为true，或者周期性的打开和关闭你的writer。
</p>
<p style="text-indent: 2em;">&#8226; 如果你要索引很多小文本字段，如果没有特别需求，建议你将这些小文本字段合并为一个大的contents字段，然后只索引contents。(当然你也可以继续存储那些字段)
</p>
<p style="text-indent: 2em;">&#8226; 加大mergeFactor合并因子，但不是越大越好
</p>
<p style="text-indent: 2em;">大的合并因子将延迟segment的合并时间，这样做可以提高索引速度，因为合并是
索引很耗时的一个部分。但是，这样做将降低你的搜索速度。同时，你有可能会用光你的文件句柄如果你把合并因子设置的太大。值太大了设置可能降低索引速度，
因为这意味着将同时合并更多的segment，将大大的增加硬盘的负担。
</p>
<p style="text-indent: 2em;">&#8226; 关闭所有你实际上没有使用的功能
</p>
<p style="text-indent: 2em;">如果你存储了字段，但是在查询时根本没有用到它们，那么别存储它们。同样Term向量也是如此。如果你索引很多的字段，关闭这些字段的不必要的特性将对索引速度提升产生很大的帮助。
</p>
<p style="text-indent: 2em;">&#8226; 使用一个更快的分析器
</p>
<p style="text-indent: 2em;">有时间分析文档将消耗很长的时间。举例来说，StandardAnalyzer就比较耗时，尤其在Lucene 2.3版本之前。你可以尝试使用一个更简单更快但是符合你需求的分析器。
</p>
<p style="text-indent: 2em;">&#8226; 加速文档的构建时间
</p>
<p style="text-indent: 2em;">在通常的情况下，文档的数据来源可能是外部（比如数据库，文件系统，蜘蛛从网站上的抓取等），这些通常都比较耗时，尽量优化获取它们的性能。
</p>
<p style="text-indent: 2em;">&#8226; 在你真的需要之前不要随意的优化optimize索引（只有在需要更快的搜索速度的时候）
</p>
<p style="text-indent: 2em;">&#8226; 在多线程中共享一个IndexWriter
</p>
<p style="text-indent: 2em;">最新的硬件都是适合高并发的（多核CPU，多通道内存构架等），所以使用多线程添加文档将会带来不小的性能提升。就算是一台很老的机器，并发添加文档都将更好的利用IO和CPU。多测试并发的线程数目，获得一个临界最优值。
</p>
<p style="text-indent: 2em;">&#8226; 将文档分组在不同的机器上索引然后再合并
</p>
<p style="text-indent: 2em;">如果你有大量的文本文档需要索引，你可以把你的文档分为若干组，在若干台机器上分别索引不同的组，然后利用writer.addIndexesNoOptimize来将它们合并到最终的一个索引文件中。
</p>
<p style="text-indent: 2em;">&#8226; 运行性能测试程序
</p>
<p style="text-indent: 2em;">如果以上的建议都没有发生效果。建议你运行下性能检测程序。找出你的程序中哪个部分比较耗时。这通常会给你想不到的惊喜。
</p>
<img src ="http://www.blogjava.net/huangfox/aggbug/332798.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huangfox/" target="_blank">fox009</a> 2010-09-25 10:33 <a href="http://www.blogjava.net/huangfox/articles/332798.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>