搜索引擎Lucene(4):索引的创建过程

1、索引维护

创建索引的过程如下:

  • 建立索引器 Indexwriter。
  • 建立文档对象 Document。
  • 建立信息字段对象 Field。
  • 将 Field 添加到 Documeni 里面。
  • 将 Document 添加到 Indexwriter里面。
  • 关闭索引器 Indexwriter。

索引结构如下:

索引结构.png

IndexWriter结构:

IndexWriter.png

IndexWriter通过指定存放的目录(Directory)以及文档分析器(Analyzer)来构建,direcotry代表索引存储在哪里;analyzer表示如何来分析文档的内容;similarity用来规格化文档,给文档算分;IndexWriter类里还有一些SegmentInfos对象用于存储索引片段信息,以及发生故障回滚等。

添加文档使用addDocument()方法,删除文档使用deleteDocuments(Term)或者deleteDocuments(Query)方法,而且一篇文档可以使用updateDocument()方法来更新(仅仅是先执行delete在执行add操作而已)。当完成了添加、删除、更新文档,应该需要调用close方法。

这些修改会缓存在内存中(buffered in memory),并且定期地(periodically)刷新到(flush)Directory中(在上述方法的调用期间)。一次flush操作会在如下时候触发(triggered):当从上一次flush操作后有足够多缓存的delete操作(参见setMaxBufferedDeleteTerms(int)),或者足够多已添加的文档(参见setMaxBufferedDocs(int)),无论哪个更快些(whichever is sooner)。对被添加的文档来说,一次flush会在如下任何一种情况下触发,文档的RAM缓存使用率(setRAMBufferSizeMB)或者已添加的文档数目,缺省的RAM最高使用率是16M,为得到索引的最高效率,你需要使用更大的RAM缓存大小。需要注意的是,flush处理仅仅是将IndexWriter中内部缓存的状态(internal buffered state)移动进索引里去,但是这些改变不会让IndexReader见到,直到commit()和close()中的任何一个方法被调用时。一次flush可能触发一个或更多的片断合并(segmentmerges),这时会启动一个后台的线程来处理,所以不会中断addDocument的调用,请参考MergeScheduler。

一个IndexReader或者IndexSearcher只会看到索引在它打开的当时的状态。任何在索引被打开之后提交到索引中的commit信息,在它被重新打开之前都不会见到。

DocumentsWriter结构:

DocumentsWriter 是由IndexWriter 调用来负责处理多个文档的类,它通过与Directory 类及Analyzer 类、Scorer 类等将文档内容提取出来,并分解成一组term列表再生成一个单一的segment 所需要的数据文件,如term频率、term 位置、term 向量等索引文件,以便SegmentMerger 将它合并到统一的segment 中去。

DocumentsWriter.png

该类可接收多个添加的文档,并且直接写成一个单独的segment 文件。这比为每一个文档创建一个segment(使用DocumentWriter)以及对那些segments 执行合作处理更有效率。

每一个添加的文档都被传递给DocConsumer类,它处理该文档并且与索引链表中(indexing chain)其它的consumers相互发生作用(interacts with)。确定的consumers,就像StoredFieldWriter和TermVectorsTermsWriter,提取一个文档的摘要(digest),并且马上把字节写入“文档存储”文件(比如它们不为每一个文档消耗(consume)内存RAM,除了当它们正在处理文档的时候)。

其它的consumers,比如FreqProxTermsWriter和NormsWriter,会缓存字节在内存中,只有当一个新的segment制造出的时候才会flush到磁盘中。

一旦使用完我们分配的RAM缓存,或者已添加的文档数目足够多的时候(这时候是根据添加的文档数目而不是RAM的使用率来确定是否flush),我们将创建一个真实的segment,并将它写入Directory中去。

索引创建的调用过程:

索引创建调用过程.png

2、索引存储类

一个Directory对象是一系列统一的文件列表(a flat list of files)。文件可以在它们被创建的时候一次写入,一旦文件被创建,它再次打开后只能用于读取(read)或者删除(delete)操作。并且同时在读取和写入的时候允许随机访问。

directory.png

2.1、FSDirectory

FSDirectory类直接实现Directory抽象类为一个包含文件的目录。目录锁的实现使用缺省的SimpleFSLockFactory,但是可以通过两种方式修改,即给getLockFactory()传入一个LockFactory实例,或者通过调用setLockFactory()方法明确制定LockFactory类。

目录将被缓存(cache)起来,对一个指定的符合规定的路径(canonical path)来说,同样的FSDirectory实例通常通过getDirectory()方法返回。这使得同步机制(synchronization)能对目录起作用。


FSDirectory.png

2.2、RAMDirectory

RAMDirectory类是一个驻留内存的(memory-resident)Directory抽象类的实现。目录锁的实现使用缺省的SingleInstanceLockFactory,但是可以通过setLockFactory()方法修改。

RAMDirector.png

2.3、IndexInput

IndexInput类是一个为了从一个目录(Directory)中读取文件的抽象基类,是一个随机访问(random-access)的输入流(input stream),用于所有Lucene读取Index的操作。BufferedIndexInput是一个实现了带缓冲的IndexInput的基础实现。

indexInput.png

2.4、IndexOutput

IndexOutput类是一个为了写入文件到一个目录(Directory)中的抽象基类,是一个随机访问(random-access)的输出流(output stream),用于所有Lucene写入Index的操作。BufferedIndexOutput是一个实现了带缓冲的IndexOutput的基础实现。RAMOuputStream是一个内存驻留(memory-resident)的IndexOutput的实现类。


indexOutput.png

3、域选项

3.1、域索引选项

域索引选项通过倒排索引来控制文本是否可被搜索。

  • Index.ANALYZED:使用分析器将阈值分解成独立的语汇单元流,并使每个语汇单元能被搜索。该选项适用普通文本域(正文、标题、摘要等);
  • Index.NOT_ANALYZED:对域进行索引,但不对String值进行分析。该操作实际上将域值作为单一语汇单元并使之能被搜索。该选项适用于索引那些不能被分解的域值,如URL、文件路径、日志、名字等。改选项尤其适用于精确匹配的搜索。
  • Index.ANALYZED_NO_NORMS:这是Index.ANALYZED选项的变体,它不会再索引中存储norms信息。norms记录了索引中的index-time boost信息,但是当你进行搜索时可能会比较耗费内存。
  • Index.NOT_ANALYZED_NO_NORMS:与Index.NOT_ANALYZED选项类似,但也是不存储norms。改选项常用于在搜索期间节省索引空间和减少内存耗费,因为single-token域并不需要norms信息,除非它们已被进行加权操作。
  • Index.NO:使对应的阈值不被搜索。

当lucene建立起倒排索引后,默认情况下它会保存所有必要的信息以实施Vector Space Model。该Model需要计算文档中出现的Term数,以及它们出现的文职(这是必要的,比如通过词组搜索时用到)。但有时候这些域只是在布尔搜索时用到,他们并不为相关评分做贡献,一个常见的例子是,域只是被用作过滤,如权限过滤和日期过滤。在这种情况下,可以通过调用Field.setOmitTermFreqAndPositions(true)方法让lucene跳过对改选项的出现频率和出现位置的索引。该方法可以节省一些索引在磁盘上的储存空间,还可以加速搜索和过滤过程,但会悄悄阻止需要位置信息的搜索,如阻止PhraseQuery和SpanQuery类的运行。

3.2、域存储选项

域存储选项是用来确定是否需要存储域的真实值,以便后续搜索时能回复这个值。

  • Store.YES:指定存储阈值。该情况下,原始的字符串值会全部被保留在索引中,并可以由IndexReader类恢复。该选项对于需要展示搜索结果的一些域很有用。如果索引的大小在搜索程序考虑考虑之列的话,不要存储太大的阈值,因为存储这些域值会消耗索引的存储空间。
  • Store.NO:指定不存储域值。该选项通常更Index.ANALYZED选项共同用来索引打的文本域值,通常这些域值不用恢复为初始格式。

3.3、域选项组合

索引选项 存储选项 项向量 使用范例
NOT_ANALYZED_NO_NORMS YES NO b标识符(文件名、主键),电话号码和社会安全号码、URL、姓名、日期
ANALYZED YES WITH_POSITION_OFFSET 文档标题、摘要
ANALYZED NO WITH_POSITION_OFFSET 文档正文
NO YES NO 文档类型、数据库主键
NOT_ANALYZED NO NO 隐藏的关键词

3.4、多域值

lucene支持想一个域中写入多个不同的值。

Document doc = new Document();
for(String author:authors){
    doc.add(new Field("author",author,Field.Store.YES,Field.Index.ANALYZED))
}

这种处理方式是完全可以接受并鼓励使用的,因为这是逻辑上具有多个域值的域的自然表示方式。在lucene内部,只要文档中出现同名的多域值,倒排索引和项向量都会在逻辑上将这些语汇单元附加进去,具体顺序由添加该域的顺序决定。

4、加权操作

文档和域的加权操作可以在索引期间完成。而搜索期间的加权操作会更加动态化,因为每次搜索都可以根据不同的加权因子独立选择加权或不加权,但这个策略也可能多消耗一些CPU效率。搜索期间的动态加权可以更灵活控制。

4.1、文档加权操作

默认情况下,所有文档的加权因子都是1.0,通过改变文档的加权因子,就可以影响文档在索引中的重要程度。调整加权操作的API为:setBoost(float);

4.2、域加权操作

同文档加权一样,可以对进行加权操作。文档加权时,lucene内部会采用同一加权因子来对该文档中的域进行加权。域加权API:Field.setBoost(fliat)。

5、文档分析器

Analyzer类构建用于分析文本的TokenStream对象,因此(thus)它表示(represent)用于从文本中分解(extract)出组成索引的terms的一个规则器(policy)。典型的(typical)实现首先创建一个Tokenizer,它将那些从Reader对象中读取字符流(stream of characters)打碎为(break into)原始的Tokens(raw Tokens)。然后一个或更多的TokenFilters可以应用在这个Tokenizer的输出上。警告:你必须在你的子类(subclass)中覆写(override)定义在这个类中的其中一个方法,否则的话Analyzer将会进入一个无限循环(infinite loop)中。

Analyzer.png

StandardAnalyzer:

StandardAnalyzer类是使用一个English的stop words列表来进行tokenize分解出文本中word,使用StandardTokenizer类分解词,再加上StandardFilter以及LowerCaseFilter以及StopFilter这些过滤器进行处理的这样一个Analyzer类的实现。

StandardAnalyzer.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容