Lucene底层高级
1. 底层存储结构
lucene的索引结构是有层次结构的:
-
索引
一个目录一个索引,在Lucene中一个索引是放在一个文件夹中的,同一文件夹中的所有的文件构成一个Lucene索引
-
段(Segment)
一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并. 在建立索引的时候对性能影响最大的地方就是在向索引写入文档的时候, 所以在具体应用的时候 就需要对此加以控制,段(Segment) 就是实现这种控制的
Lucene默认情况是每加入10份文档(Document)就从内存往index文件写入并生成一个段 (Segment) 然后每10个段(Segment)就合并成一个段(Segment). 这些控制的变量如下:
IndexWriter属性 默认值 描述 MergeFactory 10 控制segment合并的频率和大小 MaxMergeDocs Int32.MaxValue 限制每个segment中包含的文档数 MinMergeDocs 10 当内存中的文档达到多少的时候再写入segment
-
文档(Document)
文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。 新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中。
-
域(Field)
一篇文档包含不同类型的信息,可以分开索引,比如标题,内存,作者等,都可以保存在不同的域里,不同域的索引方式可以不同。
-
词(Term)
词是索引的最小单位,是经过词法分析和语言处理后的字符串。
Lucene的索引结构中,即保存了正向信息,也保存了反向信息。
所谓正向信息:
按层次保存了从索引一直到词的包含关系:索引(Index) –> 段(segment) –> 文档(Document) –> 域(Field) –> 词(Term)
也即此索引包含了那些段,每个段包含了那些文档,每个文档包含了那些域,每个域包含了那些词。
既然是层次结构,则每个层次都保存了本层次的信息以及下一层次的元信息,也即属性信息,比如一 本介绍中国地理的书,应该首先介绍中国地理的概况,以及中国包含多少个省,每个省介绍本省的基本概况 及包含多少个市,每个市介绍本市的基本概况及包含多少个县,每个县具体介绍每个县的具体情况。
所谓反向信息: 保存了词典到倒排表的映射:词(Term) –> 文档(Document)
词典的构建:
为何Lucene大数据量搜索快, 要分两部分来看:一点是因为底层的倒排索引存储结构; 另一点就是查询关键字的时候速度快 , 因为词典的索引结构
-
语典数据结构对比
倒排索引中的词典位于内存,其结构尤为重要,有很多种词典结构,各有各的优缺点,最简单如排序数组,通过二分查找来检索数据,更快的有哈希表,磁盘查找有B树、B+树,但一个能支持TB级数据的倒排索引结构需要在时间和空间上有个平衡,下图列了一些常见词典的优缺点:
数据结构 说明 排序列表Array/List 使用二分法查找,不平衡 HashMap/TreeMap 性能高,内存消耗大,几乎是原始数据的三倍 Skip List 跳跃表,可快速查找词语,在lucene、redis、Hbase等均有实现。相对于TreeMap等结构,特别适合高并发场景 Trie 适合英文词典,如果系统中存在大量字符串且这些字符串基本没有公共前缀,则相应的trie树将非常消耗内存 Double Array Trie 适合做中文词典,内存占用小,很多分词工具均采用此种算法 Ternary Search Tree 三叉树,每一个node有3个节点,兼具省空间和查询快的优点 Finite State Transducers (FST) 一种有限状态转移机,Lucene 4有开源实现,并大量使用 -
Trie(单词查找树)
优点:最大限度的减少无谓的比较,查询效率比哈希高
缺点: 空间换时间,利用字符串的公共前缀来降低查询时间的开销已达到提高查询的目的。 空间消耗比较大
-
跳表结构
Lucene3.0之前使用的也是跳跃表结构 之后换成了FST,但跳跃表在Lucene其他地方还有应用如倒排表合并和文档号索引
优点 :结构简单、跳跃间隔、级数可控
缺点 :模糊查询支持不好.
-
FST的结构
优点:内存占用率低,压缩率一般在3倍~20倍之间、模糊查询支持好、查询快
缺点:结构复杂、输入要求有序、更新不易
2. Lucene相关度排序
-
相关度排序
Lucene对查询关键字和索引文档的相关度进行打分,得分高的就排在前边。
Lucene是在用户进行检索时实时根据搜索的关键字计算出相关度得分。
-
相关度排序公式
score(q,d) : 文档d对查询q的相关性得分 coord(q,d):协调因子 一次搜索可能包含多个搜索词,而一篇文档中也可能包含多个搜索词,此 项表示,当一篇文档中包含的搜索词越多,则此文档则打分越高。 queryNorm(q):计算每个查询条目的方差和,使得不同的query之间的分数可以比较。其公式如 下:
t:Term,这里的Term是指包含域信息的Term,也即title:hello和content:hello是不同的Term tf(t in d):Term t在文档d中出现的词频 一般来说tf越大 得分越高 idf(t):Inverse Document Frequency 逆文档频率 Term t在几篇文档中出现过 值大得分低 norm(t, d): 标准化因子,是字段长度归一值,与检索时字段的Boost (如果存在)相结合。 它包括三个参数: Document boost:此值越大,说明此文档越重要。 Field boost:此域越大,说明此域越重要。 lengthNorm(field) = (1.0 / Math.sqrt(numTerms)):一个域中包含的Term总数越多,也即 文档越长,此值越小,文档越短,此值越大。是字段中词数平方根的倒数。 各类Boost值 term boost:查询语句中每个词的权重,可以在查询中设定某个词更加重要。 document boost:文档权重,在索引阶段写入文件,表明某些文档比其他文档更重要。 field boost:域的权重,在索引阶段写入文件,表明某些域比其他的域更重要。
-
向量空间模型
Lucene的打分机制是采用向量空间模型的: 我们把文档看作一系列词(Term),每一个词(Term)都有一个权重(Term weight),不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算
W(t,d):the weight of term t in document d tf(t,d):frequency of term t in document d n :total number of documents df(t):the number of documents that contain term t
-
BM25
BM25 源自 概率相关模型(probabilistic relevance model) ,而不是向量空间模型,但这个算法 也和 Lucene 的实用评分函数有很多共通之处。加入了词汇在查询向量中的权值以及在文档中的权值还有 一系列经验因子。 传统的TF值理论上是可以无限大的。而BM25与之不同,它在TF计算方法中增加了一个常量k,用来限制TF 值的增长极限。 BM25还引入了平均文档长度的概念,单个文档长度对相关性的影响力与它和平均长度的比值有关系。
3. lucene优化
-
过滤请求
有些单词不在索引库里,但还需要进索引库查询,发起不必要的IO请求。
使用布隆过滤器,预先判断单词是不是在该索引库里。布隆过滤器原理很简单,对一单词哈希,并映射到相应bit,设置为1,判断时同样做哈希,并去相应bit位取值,若为1,则可能存在,进库查询,若为0,则肯定不存在,不需进库查询。
-
通过设置IndexWriter的参数优化
现在需要通过 IndexWriterConfig 配置 setMaxBufferedDocs(int maxBufferedDocs) 控制写入一个新的segment前内存中保存的document的数目, 设置较大的数目可以加快建索引速度 但会 消耗更多的内存, 默认为10。 在MergePolicy的实现类中设置 maxMergeDocs 控制一个segment中可以保存的最大document数目, 将这个参数设置为比较大的值可以提高检索速度, 由 于该参数的默认值是整型的最大值,所以我们一般 不需要改动这个参数。 mergeFactor 合并因子 是用于控制索引片段的数量,当大于mergeFactor 设定的值时索引将合并成一个大片段。默认是 10。 更高的值意味着索引期间更低的段合并开销,但同时也意味着更慢的搜索速度, 因为此时的索引通常会包含 更多的段。故建议对程序分别进行高低多种值的测试, 利用计算机的实际性能来告诉你最优值。
-
选择合适的分词器
不同的分词器分词效果不同, 所用时间也不同。虽然StandardAnalyzer切分词速度快过IKAnalyzer, 但是由于StandardAnalyzer对中文支持不好, 所以为了追求好的分词效果, 为了追求查询时的准确率, 最好用IKAnalyzer分词器, IKAnalyzer支持停用词典和扩展词典, 可以通过调整两个词典中的内容, 来提升查询匹配的精度
-
如果有条件也可以把索引文件放入固态硬盘SSD存放,加速磁盘IO
-
屏蔽打分/排序机制
如果业务不需要打分排序机制则可以屏蔽打分排序机制
indexSearcher.setSimilarity(indexSearcher.getSimilarity(false));
-
选择合适的对象存放索引
4. 使用注意事项
-
关键词区分大小写
OR AND TO等关键词是区分大小写的,lucene只认大写的,小写的当做普通单词
-
读写互斥性
同一时刻只能有一个对索引的写操作,在写的同时可以进行搜索
-
文件锁
写索引的过程中强行退出将在tmp目录留下一个lock文件,使以后的写操作无法进行,可以将其手工删除
-
时间格式
lucene只支持一种时间格式yyMMddHHmmss,所以你传一个yy-MM-dd HH:mm:ss的时间给lucene它 是不会当作时间来处理的
DateTools.dateToString(new Date(), DateTools.Resolution.DAY) 可以把日期处理成 yyMMdd
-
设置boost
有些时候在搜索时某个字段的权重需要大一些,例如你可能认为标题中出现关键词的文章比正文中出 现关键词的文章更有价值,你可以把标题的boost设置的更大,那么搜索结果会优先显示标题中出现关键词 的文章. Query termQuery = new TermQuery(new Term("name","lucene")); BoostQuery query =new BoostQuery(termQuery, 3.5f);