Lucene底层存储结构

这是一个物理上的索引库。

这是一个逻辑上的索引库。
物理索引库中的Segment_1文件对应了逻辑索引库中的Segment段。
Segment段的文件大小有上限,达到上限后自动产生新的Segment段文件。
上限可以去使用版本的文档中查询,每个版本的上限不一样。
物理索引库中的write.lock是锁文件,保证当前只有一个线程在操作Segment文件
逻辑索引库中的词典分为三部分,分别是关键词+文档号+出现位置。关键词的大小是有限制的,最大也就是新华词典+牛津词典+文言文词典+阿拉伯数字(考虑在国内的使用)。
查询的时候会通过关键词查询到文档号,然后通过文档号对应的出现位置去定位文档。
索引库文件扩展名对照表
| 名称 | 文件扩展名 | 简短描述 |
|---|---|---|
| Segments File | segments_N | 保存了一个提交点的信息 |
| Lock File | Write.lock | 防止多个IndexWriter同时写到一份索引文件中 |
| Segment Info | .si | 保存了索引段的元数据信息 |
| Compound File | .cfs .cfe | 一个可选的虚拟文件,把所以索引信息存储到复合索引文件中 |
| Fields | .fnm | 保存fields的相关信息 |
| Field Index | .fdx | 保存指向field data的指针 |
| Field Data | .fdt | 文档存储的字段的值 |
| Term Dictonary | .tim | term词典,存储term信息 |
| Term Index | .tip | 到Term Dictionary的索引 |
| Frequencies | .doc | 由包含每个term以及频率的docs列表组成 |
| Positions | .pos | 存储出现在索引中的term的位置信息 |
| Payloads | .pay | 存储额外的per-position元数据信息 |
| Norms | .nvd .nvm | .nvm保存索引字段加权因子的元数据,.nvd保存索引字段加权因子的数据 |
| Per-Document Values | .dvd .dvm | .dvm保存索引文档评分因子的元数据,.dvd保存索引文档评分数据 |
| Term Vector Index | .tvx | 将偏移存储到文档数据文件中 |
| Term Vector Documents | .tvd | 包含有term vectors的每个文档信息 |
| Term Vector Fields | .tvf | 字段级别有关term vector的信息 |
| Live Documents | .liv | 哪些是有效文件的信息 |
| Point values | .dll .dim | 保留索引点 |
Lucene词典存储结构
倒排索引中的词典位于内存,其结构尤为重要。下面是一些常见结构
| 数据结构 | 优缺点 |
|---|---|
| 跳跃表 | 占用内存小,可调,但是对模糊查询支持不好 |
| 排序列表 | 使用二分法查找,不平衡 |
| 字典树 | 查询效率跟字符串长度有关,但只适合英文词典 |
| 哈希表 | 性能高,内存消耗大,几乎是原始数据的三倍 |
| 双数组字典树 | 适合做中文词典,内存占用小,很多分词工具均采用此种算法 |
| FST | 一种有限状态转移机,Lucene4有开源实现,并且大量使用 |
| B树 | 磁盘索引,更新方便,检索速度慢,常用于数据库 |
跳跃表
Lucene3之前使用跳跃表机制,Lucene3之后换成了FST结构。
- 优点:结构简单、跳跃间隔、级数可控,Lucene3.0之前使用的也是跳跃表结构,但跳跃表在Lucene其他地方还有应用如倒排表合并和文档号索引
- 缺点:模糊查询支持不好
在了解跳跃表查询的方式前,先看一下单链表的查询方式。
假设我们有一个单链表:
7——》14——》21——》32——》37——》71——》85——》117
现在查询出85这个节点,需要查询几次。
因为单链表中数据是有序的,所以我们不能通过二分法来降低查询所用时间,我们必须一个栈一个栈的查,也就是要查询7次。
如果使用跳跃表的机制呢?
跳跃表会随机从单链表中抽取一半数据组成第二层,然后从第二层中随机抽取一半的数据组成第三层,如果第三层数据量还是相对偏大,可以继续有第四层,第五层。抽取后如下表:
| start | 21 | 37 | end | ||||||
|---|---|---|---|---|---|---|---|---|---|
| start | 7 | 21 | 37 | 71 | end | ||||
| start | 7 | 14 | 21 | 32 | 37 | 71 | 85 | 117 | end |
第一层:查询3次,分别是21,37,end。发现85比37大,不再查询21后的元素,只查询37后的元素。
第二层:查询2次,分别是71,end。发现85比71大,继续查询。
第三层:查询1次,直接查询到85。
一共查询6次。
FST
Lucene现在采用的数据结构是FST,他的特点是:
-
优点
内存占用率高,压缩率一般在3倍-20倍之间、模糊查询支持好,查询快。
-
缺点
结构复杂,输入要求有序、更新不易。
FST要求输入时有序的,而且Lucene也会将解析出来的文档单词预先排序,然后构建FST。假设我们输入abd,abe,acf,acg,那么构建过程如下:
a
/
/
b
/
/
d
----------
a
/
/
b
/ \
/ \
d e
----------
a
/ \
/ \
b c
/ \ /
/ \ /
d e f
----------
a
/ \
/ \
b c
/ \ / \
/ \ / \
d e f g
FST使用的是二叉树,跳跃表使用的是链表。
Lucene优化
解决大量磁盘IO问题
-
设置Buffere大小,加快建索引的速度
config.setMaxBufferedDocs(100000);控制写入一个新的segment前内存中保存的documen的数目,设置较大的数目可以加快建索引的速度。
数值越大索引速度越快,但是会消耗更多的内存。
-
将多个文档合并成一个段
IndexWriter.forceMerge(文档数量):设置N个文档合并成一个段。
合并的文档数量越大索引速度越快,搜索速度越慢;合并的文档数量越小索引速度越慢,搜索速度越快。
更高的数值表示索引期间更低的合并开销,但是同样也代表更慢的搜索速度。如果设置的不是很高,可以带来很高的搜索速度,因为索引操作期间程序会利用并发机制完成合并操作。
应该对程序进行多次调整,让机器的性能告诉你最优值。
没有优化
素材有大概120W条数据。
package com.itheima.lucene;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
// 建立索引库
public class LuceneFirst {
public static void main(String[] args) throws Exception {
// 获得开始时间
long start = System.currentTimeMillis();
createIndex();
// 获得结束时间
long end = System.currentTimeMillis();
System.out.println("消耗的时间为:"+(end - start));
}
public static void createIndex() throws Exception{
// 1.创建一个Director对象,指定索引库保存的位置
Directory directory = FSDirectory.open(new File("E:\\syk").toPath());
// 2.基于Director对象创建一个IndexWriter对象
IndexWriter indexwriter = new IndexWriter(directory,new IndexWriterConfig());
// 3.读取磁盘上的文件,对应每个文件来创建一个文档对象
// - 创建一个文档对象指向需要建立索引库的文件目录
File dir = new File("E:\\searchsource");
System.out.println(dir);
// - 得到目录下的文件列表
File[] files = dir.listFiles();
// - 遍历文件列表
for(File f : files){
// - 获取文件名
String fileName = f.getName();
// - 获取文件路径
String filePath = f.getPath();
// - 获取文件内容
String fileContent = FileUtils.readFileToString(f, "utf-8");
// - 文件大小
long fileSize = FileUtils.sizeOf(f);
// - 创建Field 参数1:域的名称;参数2:域的内容;参数3:是否存储
Field fieldName = new TextField("name",fileName,Field.Store.YES);
Field fieldPath = new TextField("Path",filePath,Field.Store.YES);
Field fieldContent = new TextField("Content",fileContent,Field.Store.YES);
Field fieldSize = new TextField("Size",fileSize+"",Field.Store.YES);
// - 创建文档对象
Document document = new Document();
// 4.向文档对象中添加域
document.add(fieldName);
document.add(fieldPath);
document.add(fieldContent);
document.add(fieldSize);
// 5.将文档对象写入索引库
indexwriter.addDocument(document);
}
// 6.关闭IndexWriter对象
indexwriter.close();
}
}
消耗的时间为:7725
缓冲区优化
package com.itheima.lucene;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import<
Lucene索引机制与优化

本文深入探讨了Lucene的底层存储结构,包括物理与逻辑索引库、Segment文件的作用及大小限制,以及词典的构成。文章还介绍了Lucene的索引库文件扩展名对照表,详细解释了每种文件的用途。此外,文中对比了几种不同的数据结构,如跳跃表和FST,以及它们在Lucene中的应用。最后,文章提供了Lucene优化的实践案例,包括缓冲区大小设置、文件合并策略、分词器选择和索引库存放位置的影响。
最低0.47元/天 解锁文章
841

被折叠的 条评论
为什么被折叠?



