solr索引的概念merge

来源:互联网 发布:网络90后美女排行榜 编辑:程序博客网 时间:2024/05/19 04:55

 solr索引

当我们真正进入到Lucene源代码之中的时候,我们会发现:

• Lucene的索引过程,就是按照全文检索的基本过程,将倒排表写成此文件格式的过程。 

• Lucene的搜索过程,就是按照此文件格式将索引进去的信息读出来,然后计算每篇文档打分(score)的过程。

lucene的工作方式 

lucene提供的服务实际包含两部分:一入一出。所谓入是写入,即将你提供的源(本质是字符串)写入索引或者将其从索引中删除;所谓出是读出,即向用户提供全文搜索服务,让用户可以通过关键词定位源。

l 写入流程 

源字符串首先经过analyzer处理,包括:分词,分成一个个单词;去除stopword(可选)。 将源中需要的信息加入Document的各个Field中,并把需要索引的Field索引起来,把需要存储的Field存储起来。 将索引写入存储器,存储器可以是内存或磁盘。

l 读出流程 

用户提供搜索关键词,经过analyzer处理。 对处理后的关键词搜索索引找出对应的Document。 用户根据需要从找到的Document中提取需要的Field。

索引文件结构

Lucene的索引结构在概念上即为传统的倒排索引结构。下图就是Lucene生成的索引的一个实例:

 

Lucene的索引结构是有层次结构的,主要分以下几个层次: 

• 索引(Index): 

o 在Lucene中一个索引是放在一个文件夹中的。 

o 如上图,同一文件夹中的所有的文件构成一个Lucene索引。 

• 段(Segment): 

o 一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并。 

o 如上图,具有相同前缀文件的属同一个段,图中共两个段 "_0" 和 "_1"。 

o segments.gen和segments_5是段的元数据文件,也即它们保存了段的属性信息。 

• 文档(Document): 

o 文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。 

o 新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中。 

• 域(Field): 

o 一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,都可以保存在不同的域里。 

o 不同域的索引方式可以不同,在真正解析域的存储的时候,我们会详细解读。 

• 词(Term): 

       词是索引的最小单位,是经过词法分析和语言处理后的字符串。

Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。这看起来似乎有些废话,事实上对这两个属性的正确组合很重要,下面举例说明: 

还是以刚才的文章为例子,我们需要对标题和正文进行全文搜索,所以我们要把索引属性设置为真,同时我们希望能直接从搜索结果中提取文章标题,所以我们把标题域的存储属性设置为真,但是由于正文域太大了,我们为了缩小索引文件大小,将正文域的存储属性设置为假,当需要时再直接读取文件;我们只是希望能从搜索解果中提取最后修改时间,不需要对它进行搜索,所以我们把最后修改时间域的存储属性设置为真,索引属性设置为假。

倒排索引

索引技术主要有以下3种: 倒排索引,后缀数组和签名文件。其中, 倒排索引技术在当前大多数的信息检索系统中得到了广泛的应用, 它对于关键词的搜索非常有效, 在lucene中也是使用的这种技术。后缀数组技术在短语查询中具有很快的速度, 但是这样的数据结构在构造和维护时都比较复杂一些。签名文件技术在20世纪80年代比较流行, 但是后来倒排索引技术逐渐超越了它。

倒排索引是目前搜索引擎公司对搜索引擎最常用的存储方式, 也是搜索引擎的核心内容, 倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值, 而是由属性值来确定记录的位置, 因而称为倒排索引。倒排索引是以关键字和文档编号结合, 并以关键字作为主键的索引结构。下面利用一个例子来说明倒排索引:

 

比如说有两个文档, doc1和doc2他们的内容分别如下:

Doc1: we are students。

Doc2: Areyoustudent?

如果按照正常的索引建立如下所示:

文档名    关键字    次数

Doc1       we        1

Doc1       are        1

Doc2      student      1

Doc2       Are        1

……

这里索引的建立是以文档为标准的, 这样当文档很多

的时候数据量将非常的大, 检索效率会明显下降的。倒排索引是以单词为标准来进行索引的建立的。还以上面的doc1和doc2为例:

关键字    出现的文档    次数

student      doc2          1

we         doc1         1

Are        doc1   doc2   1  1

……

索引建立

你可能已经注意到,即使solr.xml已经被POST到服务器两次,但你搜索"solr"时仍然只得到一个结果。这是因为schema.xml例子文件指定了一个"uniqueKey"字段作为"id"。无论何时你向Solr发送指令添加一个文档,如果已经存在一个uniqueKey相同的文档,它会自动地为你替换。

Solr建立索引和对关键词进行查询都得对字串进行分词,在向索引库中添加全文检索类型的索引的时候,Solr会首先用空格进行分词,然后把分词结果依次使用指定的过滤器进行过滤,最后剩下的结果才会加入到索引库中以备查询。分词的顺序如下:

l 索引   

1:空格whitespaceTokenize     

2:过滤词StopFilter    

3:拆字WordDelimiterFilter     

4:小写过滤LowerCaseFilter     

5:英文相近词EnglishPorterFilter  

6:去除重复词RemoveDuplicatesTokenFilter     

l 查询  

1:查询相近词     

2:过滤词     

3:拆字     

4:小写过滤     

5:英文相近词     

6:去除重复词 

在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤。在例子中FieldType在定义的时候,在index的analyzer中使用 solr.WhitespaceTokenizerFactory这个分词包,就是空格分词,然后使用 

solr.StopFilterFactory(去掉如下的通用词,多为虚词。 

   "a", "an", "and", "are", "as", "at", "be", "but", "by", 

    "for", "if", "in", "into", "is", "it", 

    "no", "not", "of", "on", "or", "s", "such", 

    "t", "that", "the", "their", "then", "there", "these", 

    "they", "this", "to", "was", "will", "with"),

solr.WordDelimiterFilterFactory,关于分隔符的处理。

solr.RemoveDuplicatesTokenFilterFactory避免重复处理。

solr.StandardFilterFactory 移除首字母简写中的点和Token后面的’s。仅仅作用于有类的Token,他们是由StandardTokenizer产生的。 

例:StandardTokenizer+ StandardFilter 

"I.B.M. cat's can't" ==> "IBM", "cat", "can't"

这几个过滤器。在向索引库中添加类型的索引的时候,Solr会首先用空格进行分词,然后把分词结果依次使用指定的过滤器进行过滤,最后剩下的结果才会加入到索引库中以备查询。

 

索引存储

1. 前缀后缀规则(Prefix+Suffix) 

Lucene在反向索引中,要保存词典(Term Dictionary)的信息,所有的词(Term)在词典中是按照字典顺序进行排列的,然而词典中包含了文档中的几乎所有的词,并且有的词还是非常的长 的,这样索引文件会非常的大,所谓前缀后缀规则,即当某个词和前一个词有共同的前缀的时候,后面的词仅仅保存前缀在词中的偏移(offset),以及除前 缀以外的字符串(称为后缀)。

 

比如要存储如下词:term,termagancy,termagant,terminal,

如果按照正常方式来存储,需要的空间如下:

[VInt = 4] [t][e][r][m],[VInt = 10][t][e][r][m][a][g][a][n][c][y],[VInt = 9][t][e][r][m][a][g][a][n][t],[VInt = 8][t][e][r][m][i][n][a][l]

共需要35个Byte.

如果应用前缀后缀规则,需要的空间如下:

[VInt = 4] [t][e][r][m],[VInt = 4 (offset)][VInt = 6][a][g][a][n][c][y],[VInt = 8 (offset)][VInt = 1][t],[VInt = 4(offset)][VInt = 4][i][n][a][l]

共需要22个Byte。

大大缩小了存储空间,尤其是在按字典顺序排序的情况下,前缀的重合率大大提高。

2. 差值规则(Delta) 

在Lucene的反向索引中,需要保存很多整型数字的信息,比如文档ID号,比如词(Term)在文档中的位置等等。

由上面介绍,我们知道,整型数字是以VInt的格式存储的。随着数值的增大,每个数字占用的Byte的个数也逐渐的增多。所谓差值规则(Delta)就是先后保存两个整数的时候,后面的整数仅仅保存和前面整数的差即可。

 

比如要存储如下整数:16386,16387,16388,16389

如果按照正常方式来存储,需要的空间如下:

[(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0011][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0100][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0101][(1) 000, 0000][(0) 000, 0001]

供需12个Byte。

如果应用差值规则来存储,需要的空间如下:

[(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001]

共需6个Byte。

大大缩小了存储空间,而且无论是文档ID,还是词在文档中的位置,都是按从小到大的顺序,逐渐增大的。

索引压缩

为了减小索引文件的大小,Lucene对索引还使用了压缩技术。首先,对词典文件中的关键字进行了压缩,关键字压缩为<前缀长度,后缀>。例如:当前词为马来西亚语,上一个词为马来西亚,那么马来西亚语压缩为<4,语>。其次大量用到的是对数字的压缩,数字只保存与上一个值的差值(这样可以减小数字的长度,进而减少保存该数字需要的字节数)。例如,当前文章号是14569(不压缩要用3个字节保存),上一文章号是145704,压缩后保存5(只用一个字节)。

Lucene 的域数据文件(.fdt 文件)在一定情况下采用了 ZLIB压缩算法,这是一种无损的数据压缩格式,且独立于具体的CPU 类型、操作系统、文件格式以及字符集。但是它的弱点是对压缩数据不支持随机访问。在Lucene 中是通过调用 JDK 库文件中的Deflater类来实现ZLIB 压缩的(参见index 包下的FieldsWriter类)。

差值压缩(delta compression)是 Lucene 广泛采用的另一种压缩方式,这一方法是基于排序的字符通常具有相同的前缀部分,只把一个块中与第一个词不同的部分存储在索引里显然能够节省存储空间。例如第一个单词位“automata ”有 个字符,接下来的单词“automate ”也有 个字符,下一个单词“automatic ”个字符,再下一个单词“automation ”10 个字符在存储的时候按如下的方式存储:8{automat}a1<>e2<>ic3<>ion。其中的数字表示与前一个单词不同的个数。

索引优化

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

1.合并因子(mergeFactor)

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

2.最小合并文档数

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

3.最大合并文档数

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

通过表 1,你可以清楚地看到三个参数对索引时间的影响。在实践中,你会经常的改变合并因子和最小合并文档数的值来提高索引性能。只要你有足够大的内存,你可以为合并因子和最小合并文档数这两个参数赋尽量大的值以提高索引效率,另外我们一般无需更改最大合并文档数这个参数的值,因为系统已经默认将它设置成了最大。

优化索引就是把磁盘上的多个索引文件合并在一起,以便减少文件的数量,从而也减少搜索索引的时间。需要注意的是索引优化并不能提高索引的速度,而只能提高搜索的速度;并且索引优化只有在索引处理完在一定时间不会发生变化的时候进行。 

Lucene 采用在内存中缓存多个文档,在写入磁盘前把这些文档合并为段。并且可以通过 mergeFcatormaxMergeDocsminMergeDocs控制所要合并的文档的数量。 mergeFactor是用于控制Lucene 在把索引从内存写入磁盘上的文件系统时内存中最大的 Document数量、同时它还控制内存中最大的Segment 数量。mergeFactor这个参数设置会严重影响到Lucene 建立索引时花费的词盘I/O 时间和内存使用量,如果 mergeFactor值设置的太小,则磁盘的 I/O 操作太频繁,经常进行Segment 的合并。而如果 mergeFactor值设置的太大,则内存中持有的 Document数量可能会很多,因此占用大量的内存。maxMergeDocs 用来限制一个Segment 中最大的文档数量。例如:当 mergeFactor设置为10 maxMergeDocs设置为2000 是,第一次合并,Segment 中的文档数量可能为 100 ,再合并时,一个 Segmnet中的文档数量就会为1000 ,此时由于受到 maxMergeDocs的限制,一个 Segment 中的文档数量最多不超过2000,因此下一次合并将发生在 2000 文档时。 minMergeDocs用于控制内存中持有的文档数量,也就是说,内存中文档被到磁盘前的数量。 

Lucene 可以通过 FSDirectory 把索引存储在磁盘目录中,也可以通过RAMDirectory使用内存缓冲的形式操作索引文件,以便进一步控制索引的合并。RAMDirectory的操作与FSDirectory 的操作类似,但是速度更快,并且RAMDirectory不把具体的索引写入磁盘目录,一旦程序退出,索引文件就不存在。为此Lucene提供了addIndexes 函数把内存索引文件写入磁盘目录文件。

 

 

commit就是提交数据,就是更新。  

curl 'http://localhost:8983/solr/update?optimize=true&maxSegments=10&waitFlush=false'

索引提交

这个设置用于控制什么时候将更新的数据写入索引,该设置由以下两个子参数

maxDocs当索引的文档达到这个数量时,就自动更新到索引中

maxTime单位为毫秒,当离上一次 commit 超过这个时间后,将自动最近更新的文档写入索引中

只要达到上面任一条件,solr 都将自动最近更新的文档写入索引中,如果没有指明 autoCommit 这个参数,只能通过显示调用 commit 才能将最近更新的文档写入索引中。设置这个参数时,就需要最性能和准确度之间做个权衡,频繁提交,可以提高数据准确性,但是对性能有一定的损耗。

<autoCommit>

    <maxDocs>10000</maxDocs>

    <maxTime>1000</maxTime>

</autoCommit>

 

 

建立索引:堆排序,hashjoin 

最简单的能完成索引的代码片断

IndexWriter writer = new IndexWriter(“/data/index/”, new StandardAnalyzer(), true); 

Document doc = new Document(); 

doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED)); 

doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED)); 

writer.addDocument(doc); 

writer.optimize(); 

writer.close();

下面我们分析一下这段代码。 

首先我们创建了一个writer,并指定存放索引的目录为“/data/index”,使用的分析器为StandardAnalyzer,第三个参数说明如果已经有索引文件在索引目录下,我们将覆盖它们。 

然后我们新建一个document。 

我们向document添加一个field,名字是“title”,内容是“lucene introduction”,对它进行存储并索引。 

再添加一个名字是“content”的field,内容是“lucene works well”,也是存储并索引。 

然后我们将这个文档添加到索引中,如果有多个文档,可以重复上面的操作,创建document并添加。 

添加完所有document,我们对索引进行优化,优化主要是将多个segment合并到一个,有利于提高索引速度。 

随后将writer关闭,这点很重要。

0 0
原创粉丝点击