分享

lucene相关博客

 niefeng2011 2014-02-28

分类: Lucene.net 搜索引擎 管理-营销-推广----艺术 1121人阅读 评论(1) 收藏 举报

 

关于lucene的笔记一。

 

 这个东西在2006年初,我就开始在项目中使用.我对它也有了一些了解. 但因为主要开发还是小兵们在做. 所以仅仅了解了一些皮毛. 下面我将以知识点的形式, 列出来. 以笔记的形式连载. 也方便大家一起学习. 每一个点, 我都会写一个知识点.

 

  1, 2005年的时候, 听说了lucene. 是一个开源的搜索引擎开发包. 而不是一个搜索引擎,请切记.

 

  2, 如果开始学习它, 就需要至少知道,它所包含的包. 目前lucene已经到了2.2版本. 当然你需要时刻关注他的最新版本. 目前包: lucene-core-2.2.0.jar . 下载可以到apache的网站上下载. 这一个就够了.不用下别的.

 

  3, 下面问题会接踵而至, 我挨着写,你挨着看即可.

 

  分词. 第一个要涉及的问题, 分词就是将一句话中的关键词汇分离出来, 然后才可以建立索引. 例如 中华人民共和国 --> 中华, 中华人民 华人,人民, 共和国,等. lucene缺省带了一个标准分词的类: StandardAnalyzer 这个按字来分的. 从网上发现了很多程序员写的开源的分词的类. 当然都是继承了lucene的org.apache.lucene.analysis.Analyze类. 以实现更好更快的分词效果. 可以搜索获取更多, 一般分词的类,都提供了可检测分词效果的方法. 输入一个长句, 然后执行,看看分词效果和执行时间.

 

  4, ThesaurusAnalyzer是一个哥们开发的,网上有源码可以下载. 从这个源码里面对分词可以有更深入的了解: 包括那些是词汇,那些不是词汇. 都在文本文件里面以行分割开来. 由此可以知道: 分词是需要词库的. 因为词库可以不断的扩充 .但每次构造分词对象时,是建立在当前词库基础上的. 如果词库动态增加了新的词汇, 需要重新构建分词对象. 当然, 也可以读取数据库.

 

  5, 上面的分词, 也仅仅是分词! 网上有人提出的问题是: 索引中,加入了"东北大学". "北大" . 要搜索 北大 , 显然我们没有找到东北大学的意思. 但最后还是找到了. 因为东北大学四个字里面有北大两个字. 分词时这个词被确认是个词, 就加入了索引. 这种情况, 涉及到汉语语义的问题 .暂时不好解决. 所以不提.

 

 选择较好的分析器

 

  这个优化主要是对磁盘空间的优化,可以将索引文件减小将近一半,相同测试数据下由600M减少到380M。但是对时间并没有什么帮助,甚至会需要更长时间,因为较好的分析器需要匹配词库,会消耗更多cpu,测试数据用StandardAnalyzer耗时133分钟;用MMAnalyzer耗时150分钟。

 

  6, 分词的缺失: 就似乎同义词. 为了减少用户搜索的次数, 增加搜索效果. 如果用户搜 "北京 饭店" 能不能把" 首都 饭店"也列出来呢. 这个分词器无能为力. 我也考虑到这个问题, 在北京托尔四公司的TRS的搜索产品文档中人家也考虑到了这个问题. 就似乎如果搜索 锐器, 系统会自动把匕首,尖刀等词汇一并加入搜索结果. 所以这个问题 ,就只能是在分词之前,我们再加一层:同义词返回模块. 这个思路很不错, 也比较简单. 很容易实现. 关键是词库的建立. 这个就说到这里.

 

  7, 说到这里,你可能想要做个例子来实践一下. 做个例子很容易. 网上很多. 我只做简单的叙述: lucene是用目录或者内存来存储数据的. 可以设定. 但是实践证明RAMDirectory和FSDirectory速度差不多,当数据量很小时两者都非常快,当数据量较大时(索引文件400M)RAMDirectory甚至比FSDirectory还要慢一点,这确实让人出乎意料。

 

  而且lucene的搜索非常耗内存,即使将400M的索引文件载入内存,在运行一段时间后都会out of memory,所以个人认为载入内存的作用并不大。

 

  我们用目录:

 

  如下:


//构建一个 IndexWriter 用来写如索引

File indexDir = new File(

"E:javasourceLuceneTestindex");

IndexWriter indexWriter = new IndexWriter(indexDir,

new ThesaurusAnalyzer(), false);

Document doc = Document(new Article("name"+i, "北京老张"));

indexWriter.addDocument(doc);

indexWrite.close();

 

 

 

 

由上可以看出, lucene将在这个目录下进行操作. 上面代码中的你不要抄袭当例子, 因为还有一个Article类和Document方法.里面也有一些东西. 现在仅仅先理解上面的意思即可. 操作前,你可能不知道他会在目录里干什么.

  8, 目录下的东西 . 如果测试成功, 目录下有三个文件.

  segments.gen segments_a08, 还有一个类似 _uw.cfs名字的东西. 当然,不一定都一样, 但肯定是这三个. 如果出现了很多文件.不要着急, 看下面的 9 .

  9, 如果lucene的索引目录下出现了很多文件, 肯定是有问题的. 几个方面.首先lucene在执行写操作时, 会先在目录下写如一个write.lock的文件锁定这个目录,以避免别的索引再操作这个路径. 否则那样肯定会乱. 锁定之后, 开始写索引, 写索引时lucene建了几个或者几十个临时片段文件, 都似乎又短又乱的字符.cfs的文件. 当索引建立完毕后,没有执行 indexWriter.optimize();方法, 他就不会合并那些乱七八糟的文件. 所以,索引建完后, 一定要执行 上面的优化方法, 保持目录下保留3个文件即可. 也就是很多临时文件会合并到一个文件中去. 切不可大意删除. 但当数据很多时, 另行考虑策略.

  10, lucene在写入索引时, 用在索引目录下建write.lock文件来标识锁定. 而只有在执行close()方法后, 才会删除这个锁文件. 只要这个文件存在, 其他的写索引的程序都会报错:

caught a class org.apache.lucene.store.LockObtainFailedException
with message: Lock obtain timed out: SimpleFSLock@E:javasourceLuceneTestindexwrite.lock

  所以,需要注意, 一定要注意关闭indexWrite. 包括异常下,用finally关闭.否则会导致下一次写索引失败.

  11, 批量增加索引, 如果要成批的用循环加入索引,该怎么办呢. 首先请注意: IndexWriter indexWriter = new IndexWriter(indexDir,

 

  new ThesaurusAnalyzer(), false); 最后一个参数为false表示持续想索引增加数据. 如果为true, 则每次会删除全部, 重新开始.

  12, 在批量增加索引时, 程序可以一直执行

  indexWriter.addDocument(doc); 但不能一直执行优化:indexWriter.optimize(); 因为优化方法比较耗时, 特别是当索引很大时, 更要注意. 因为优化, 也仅仅似乎优化会消耗很多时间和cpu. 所以这个时候.多几个文件也没关系. 网上有个人问了这样的问题, 我摘录如下, 用等号分割开我的内容:

  引自:

  http://www./topic/107818?page=3

13, 索引过程中的任意时刻、任意进程都能对索引文件进行优化,而且这样做也不会损坏索引文件或使其不能被搜索,但是在索引过程中对索引进行优化的做法并不值得提倡。优化操作的最佳时机是在索引过程结束之后,且当你确认在此后的一段时间内不会对索引文件进行更改的时候。在索引过程中进行优化只会使优化操作耗费更多的时间。(请大家汲取这个思想) 



14, 还是优化, 些人问: 我用lucene做了一个Search Engine 
程序运行也很正常,但如果连续运行几个月,有时会出现磁盘空间不足的情况 . 
通过iw.addDocument(doc)写入index 
当list里的东西全部被写入完毕后,通过optimze来优化索引, 
可这个东西运行不是很稳定,有的时候很正常,运行几个月都okay,有时1个月就出现问题了。会在index的目录里出现很多文件。这些文件似乎是应该被optimize掉的。 一个索引只能有一个indexreader, 在optimize的时候可以有多个indexsearcher在工作。 
你得确保 
-->optimize确实调用了 
-->optimize的时候, 得有双倍的磁盘空间. 可见优化的代价. 



15 ,面的lucene都是在一个目录里面的, 大家也都看到了. 也就是如果这个文件一直很大怎么办. 首先碰到的第一个问题是就是文件大小限制. 首先面临的是一个大目录问题. 

16, lucene的性能测试: 
下面给出一些测试数据,如果你觉得可以接受,那么可以选择。 
测试一:250万记录,300M左右文本,生成索引380M左右,800线程下平均处理时间300ms。 
测试二:37000记录,索引数据库中的两个varchar字段,索引文件2.6M,800线程下平均处理时间1.5ms。 


17 . 分布搜索 

我们可以使用 MultiReader 或 MultiSearcher 搜索多个索引库。 

MultiReader reader = new MultiReader(new IndexReader[] { IndexReader.Open(@"c:/index"), IndexReader.Open(@"//server/index") }); 
IndexSearcher searcher = new IndexSearcher(reader); 
Hits hits = searcher.Search(query); 

或 

IndexSearcher searcher1 = new IndexSearcher(reader1); 
IndexSearcher searcher2 = new IndexSearcher(reader2); 
MultiSearcher searcher = new MultiSearcher(new Searchable[] { searcher1, searcher2 }); 
Hits hits = searcher.Search(query); 

还可以使用 ParallelMultiSearcher 进行多线程并行搜索。 

18. 合并索引库 

将 directory1 合并到 directory2 中。 
Directory directory1 = FSDirectory.GetDirectory("index1", false); 
Directory directory2 = FSDirectory.GetDirectory("index2", false); 

IndexWriter writer = new IndexWriter(directory2, analyzer, false); 
writer.AddIndexes(new Directory[] { directory }); 
Console.WriteLine(writer.DocCount()); 
writer.Close(); 

19. 显示搜索语法字符串 

我们组合了很多种搜索条件,或许想看看与其对等的搜索语法串是什么样的。 
BooleanQuery query = new BooleanQuery(); 
query.Add(query1, true, false); 
query.Add(query2, true, false); 
//... 

Console.WriteLine("Syntax: {0}", query.ToString()); 

输出: 
Syntax: +(name:name* value:name*) +number:[0000000000000000b TO 0000000000000000d] 

呵呵,就这么简单。 

20. 操作索引库 

删除 (软删除,仅添加了删除标记。调用 IndexWriter.Optimize() 后真正删除。) 
IndexReader reader = IndexReader.Open(directory); 

// 删除指定序号(DocId)的 Document。 
reader.Delete(123); 

// 删除包含指定 Term 的 Document。 
reader.Delete(new Term(FieldValue, "Hello")); 

// 恢复软删除。 
reader.UndeleteAll(); 

reader.Close(); 

增量更新 (只需将 create 参数设为 false,即可往现有索引库添加新数据。) 
Directory directory = FSDirectory.GetDirectory("index", false); 
IndexWriter writer = new IndexWriter(directory, analyzer, false); 
writer.AddDocument(doc1); 
writer.AddDocument(doc2); 
writer.Optimize(); 
writer.Close(); 

21. 优化 

批量向 FSDirectory 增加索引时,增大合并因子(mergeFactor )和最小文档合并数(minMergeDocs)有助于提高性能,减少索引时间。 

IndexWriter writer = new IndexWriter(directory, analyzer, true); 

writer.maxFieldLength = 1000; // 字段最大长度 
writer.mergeFactor = 1000; 
writer.minMergeDocs = 1000; 

for (int i = 0; i < 10000; i++) 

// Add Documentes... 


writer.Optimize(); 
writer.Close(); 

相关参数说明 


转自《深入 Lucene 索引机制》 

利用 Lucene,在创建索引的工程中你可以充分利用机器的硬件资源来提高索引的效率。当你需要索引大量的文件时,你会注意到索引过程的瓶颈是在往磁盘上写索引文件的过程中。为了解决这个问题, Lucene 在内存中持有一块缓冲区。但我们如何控制 Lucene 的缓冲区呢?幸运的是,Lucene 的类 IndexWriter 提供了三个参数用来调整缓冲区的大小以及往磁盘上写索引文件的频率。 

22.合并因子 (mergeFactor) 

这个参数决定了在 Lucene 的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。比如,如果合并因子的值是 10,那么当内存中的文档数达到 10 的时候所有的文档都必须写到磁盘上的一个新的索引块中。并且,如果磁盘上的索引块的隔数达到 10 的话,这 10 个索引块会被合并成一个新的索引块。这个参数的默认值是 10,如果需要索引的文档数非常多的话这个值将是非常不合适的。对批处理的索引来讲,为这个参数赋一个比较大的值会得到比较好的索引效果。 

23.最小合并文档数 (minMergeDocs) 

这个参数也会影响索引的性能。它决定了内存中的文档数至少达到多少才能将它们写回磁盘。这个参数的默认值是10,如果你有足够的内存,那么将这个值尽量设的比较大一些将会显著的提高索引性能。 

24.最大合并文档数 (maxMergeDocs) 

这个参数决定了一个索引块中的最大的文档数。它的默认值是 Integer.MAX_VALUE,将这个参数设置为比较大的值可以提高索引效率和检索速度,由于该参数的默认值是整型的最大值,所以我们一般不需要改动这个参数。 

25 , 根据官方文档:从中可以分析出,如果在optimize索引的时候,也同时使用Searcher。索引空间的使用情况如下: 
1. 原始索引 
2. 由IndexWriter使用的索引,用于optimize 
3. 由IndexSearcher使用的索引,用于搜索。 

所以要三倍的正常空间. 

为什么需要优化? 
尽管未经优化的索引在大多数应用程序中都能够很好地进行工作,但在那些处理大批量索引的应用程序中,使用优化过的索引会给应用程序带来更多的好处。特别是在搜索操作需要长时间打开多个索引文件的情况下,更能体现出索引被优化后的优势,因为使用优化过的索引可以减少需要打开的文件描述符的个数 

优化所需的磁盘空间 
值得指出的是,Lucene对一个索引的优化操作是通过把已存在的段合并成一个全新的段来完成的,这些已存在段的内容最终会在新的段中表示出来。因而在进行优化时,使用的磁盘空间会有明显的增加。在新段创建完成时,Lucene删除并移除这些旧段。因此,在旧的段还没有被删除之前,索引占用的磁盘空间会变成原来的两倍,因为此时新段和旧段都会存储在索引中。在优化完成后,所占用的磁盘空间会降回到优化前的状态。请记住,索引优化的对象可以是多文件索引或复合索引。 



26 , 索引文件有大小限制吗?(翻译) 

某些 32 位操作系统限制每个文件不能大于 2GB。 

解决方法: 
1. 使用 IndexWriter.setMaxMergeDocs() 减小 MaxMergeDocs 数值。 
2. 使用多个索引库。使用复合索引. 


27, IndexWriter.SetUseCompoundFile(true) 有什么用? 

在创建索引库时,会合并多个 Segments 文件到一个 .cfs 中。此方式有助于减少索引文件数量,减少同时打开的文件数量。原因: 
某些操作系统会限制同时打开的文件数量。 

解决方法: 
1. 使用 IndexWriter's setUseCompoundFile(true) 创建复合文件,减少索引文件数量。 
2. 不要将 IndexWriter's mergeFactor 的值设置过大。尽管这能加快索引速度,但会增加同时打开的文件数量。 
3. 如果在搜索时发生该错误,那么你最好调用 IndexWriter.Optimize() 优化你的索引库。 
4. 确认你仅创建了一个 IndexSearcher 实例,并且在所有的搜索线程中共用。(原文:"Make sure you only open one IndexSearcher, and share it among all of the threads that are doing searches -- this is safe, and it will minimize the number of files that are open concurently. " 晕~~~,究竟要怎么做? ) 

28, 为什么搜索不到结果?(翻译) 

可能原因: 
. 搜索字段没有被索引。 
. 索引库中的字段没有分词存储,无法和搜索词语进行局部匹配。 
. 搜索字段不存在。 
. 搜索字段名错误,注意字段名称区分大小写。建议对字段名进行常量定义。 
. 要搜索的词语是忽略词(StopWords)。 
. 索引(IndexWriter)和搜索(IndexSearcher)所使用的 Analyzer 不同。 
. 你所使用的 Analyzer 区分大小写。比如它使用了 LowerCaseFilter,而你输入的查询词和目标大小写不同。 
. 你索引的文档(Document)太大。Lucene 为避免造成内存不足(OutOfMemory),缺省仅索引前10000个词语(Term)。可以使用 IndexWriter.setMaxFieldLength() 调整。 
. 确认在搜索前,目标文档已经被添加到索引库。 
. 如果你使用了 QueryParser,它可能并没有按照你所设想的去分析 BooleanQuerySyntax。 

如果还不行,那么: 

. 使用 Query.ToString() 查看究竟搜索了些什么。 
. 使用 Luke 看看你的索引库究竟有什么。 

29, 上面的luke是查看索引库的, 我下载了一个版本, 居然总提示我给他的索引路径不对.无奈. . 

30 , 

QueryParser 是线程安全的吗? 

不是。 

31, 说说分布式数据存储. 支持lucene的分布式搜索就是 hadoop. 这个也是apache下的属于lucene的开源项目. 但目前的我看只支持linux的分布机器. 网上很多这个方面的. 据说开发hadoop的人已经去了yahoo. 让hadoop支撑雅虎的分布式搜索. 所以按说功能强大.2006年的一月份Nutch和Lucene的缔造者Doug Cutting加入了Yahoo公司,从那时起,Yahoo就开始进行Hadoop的部署与研究. 但网上有人说, 这个分布式效率不高. 

参数如下: 

经测试,Hadoop并不是万用灵丹,很取决于文件的大小和数量,处理的复杂度以及群集机器的数量,相连的带宽,当以上四者并不大时,hadoop优势并不明显。 
比如,不用hadoop用java写的简单grep函数处理100M的log文件只要4秒,用了hadoop local的方式运行是14秒,用了hadoop单机集群的方式是30秒,用双机集群10M网口的话更慢,慢到不好意思说出来的地步。 

评: 怎么评价上面的哥们测试结果呢. 但愿他说的不对. 因为如果这样, yahoo不就死了.. 

32, MaxDoc() 和 DocCount()、NumDocs() 有什么不同? 

MaxDocs() 表示索引库中最大的 Document ID 号,由于中间的某些 Document 可能被删除,因此不能使用 MaxDocs() 来表示 Document 数量。IndexWriter.DocCount()、IndexReader.NumDocs()、IndexSearcher.Reader.NumDocs() 都表示索引库中 Document 数量。 

33, 为什么同时进行搜索和更新会引发 FileNotFoundException 异常?(翻译) 

可能原因: 
1. 某个搜索或更新对象禁用了锁。 
2. 搜索和更新使用了不同的 lockDir。 
3. 索引库被存放在 NFS (or Samba) 文件系统上。 

尽管搜索是只读操作,但 IndexSeacher 为了获取索引文件列表,也必须打开时锁定索引库。如果锁没有正确设置,那么它将取回一个错误的文件列表(此时 IndexWriter 可能正在添加或优化索引),从而导致该异常发生。 

34, write.lock 有什么用?哪些类会用到它?(翻译) 

write.lock 用来协调索引库的并发修改处理。 
当 IndexWriter 打开索引库,或者 IndexReader 删除文档时都将创建该锁。 

35. commit.lock 文件有什么用?哪些类会用到它?(翻译) 

commit.lock 在调整索引库 segments 文件内容时使用。 IndexReader 和 IndexWriter 都会使用到它。 

36, 如何更新已经索引的文档? (翻译) 

你只能先删除,然后添加更新后的文档。 

使用 IndexWriter.addIndexes(IndexReader[]) 和 IndexWriter.addIndexes(Directory[]) 合并索引库有什么不同? (翻译) 

使用 Directory[] 参数所需的文件句柄和内存较小,索引文件仅需打开一次,而使用 IndexReader[] 参数则需要打开所有的索引库。 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多