lucene搜索引擎的使用及原理分析
从基本概念、使用示例、原理解析三方面,记录lucene学习笔记,以备后面查阅。
首先,lucene是什么
Apache开源项目,Java实现,高性能、可扩展的全文检索引擎工具库
提供一套简单易用的索引/检索/全文分析等API
专注于文本索引和检索,不是一个完整的全文检索引擎
那么,lucene可以怎么用
嵌入应用程序中实现搜索功能
以此为基础构建完整的全文检索引擎:ElasticSearch / Solr(类比跑车与引擎)
Index
索引,类比关系型数据库的表,但无schema约束
Document
文档,类比行,唯一ID seqNo,或叫 docId
Field
域,一个文档包含一个或多个不同命名的域,每个域有一个域名和对应的域值
Term
项,索引和检索的最小单位
Segment
先写内存buffer,然后flush为一个Segment,docId 在Segment内唯一
本质是建立倒排索引,从Term到Term字典;
检索可看作是一个两阶段查询,从Term找到DocId,从DocId取到Doc。
索引-写-构建
1、指定索引目录
Directory directory = FSDirectory.open(Paths.get("索引目录"));
2、指定分词器
Analyzer analyzer = new StandardAnalyzer();
3、创建索引配置
IndexWriterConfig config = new IndexWriterConfig(analyzer);
4、创建Writer
IndexWriter indexWriter = new IndexWriter(directory, config);
5、写入文档
Document document = new Document();
document.add(new TextField("title", "test", Field.Store.NO));
document.add(new TextField("body", "the program has a lot of errors", Field.Store.YES));
indexWriter.addDocument(document);
6、提交
indexWriter.commit();
索引-读-检索
1、指定索引目录
Directory directory = FSDirectory.open(Paths.get("索引目录"));
2、创建Reader
IndexReader indexReader = DirectoryReader.open(directory);
3、创建Searcher
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
4、构建查询对象
Query query = new QueryParser("title", new StandardAnalyzer()).parse("test");
5、检索
TopDocs topDocs = indexSearcher.search(query, 10);
6、获取目标文档
Document one = Arrays.stream(topDocs.scoreDocs).findFirst().map(scoreDoc -> indexSearcher.doc(scoreDoc.doc)).orElse(null);
-
索引-读-查询语法
查询对象:Query
项查询:TermQuery,某个Document某个域Field是否包含某个Term
组合查询:BooleanQuery,指定条件 BooleanClause.Occur
通配符查询:WildcardQuery,* 零个或多个,? 一个,\ 转义
前缀查询:PrefixQuery
模糊查询:FuzzyQuery,编辑距离算法,计算字符串转换需要的最少操作数
短语查询:PhraseQuery,指定Term及相对位置
查询解析器:QueryParser
and , or , +, -, *, ?, (), ~ 等
1. 索引目录
Directory类:索引文件存储
SimpleFSDirectory:java io api,无法很好支持多线程操作
NIOFSDirectory:java nio api,FileChannel 位置读写,非windows平台很好支持多线程
MMapDirectory:内存映射,消耗虚拟内存空间
RAMDirectory:写入内存
FileSwitchDirectory:主目录和扩展目录切换
FSDirectory.open() 的默认返回:linux平台 mmap,非windows平台 nio,windows ram
2. 索引文件
3. 索引锁
线程安全
同一个索引可被任意数量的只读的IndexReader打开
IndexWriter与IndexWriterConfig互相绑定
IndexWriter使用的索引目录排他
文件锁,write.lock
FSLockFactory类
NativeFSLockFactory:默认,使用java nio FileLock,获取write.lock文件上的锁,安全,不适合NFS文件系统
SimpleFSLockFactory:使用Files.createNewFile,异常情况下不安全,因为锁文件write.lock不会自动删除
SingleInstanceLockFactory:内存中维护单一实例,RAMDirectory默认
以更新文档为例,简单的调用流程图:
三个关键流程:
刷盘 Flush:索引数据从内存Buffer刷到索引目录,减少内存占用
提交 Commit:产生Segment文件,保存一个提交点所有有效的索引数据,异常可回滚至上一提交状态,提交后新增索引数据可被检索
合并 Merge:将小的段文件合并,减少文件大小及数量,提高检索效率
另,
写线程类 DocumentsWriterPerThread (DWPT) ,一个IndexWriter下多个线程操作索引时,每个线程获得一个DWPT实例
写线程池 DocumentsWriterPerThreadPool,一个DWPT的对象池结构,使用Dequeue队列存储DWPT,使用LIFO顺序取DWPT
segments_N文件
可独立被查询,不可修改
包含当前索引目录中所有的索引信息
每个Segment_N文件代表某次commit()时的索引状态
每个segment文件对应一个SegmentReader,检索时读多个segment数据进行合并
非实时/近实时检索
DirectoryReader.open(final Directory directory)
非实时,因为每次打开Readers时,是读索引目录Directory中N值最大的segment_N文件,即最近的一次commit,获取已经提交的索引信息,通过IndexWriter新增的索引数据无法获知
DirectoryReader.open(final IndexWriter indexWriter)
近实时,因为IndexWriter写入时会打开索引目录,所以通过writer获取reader,既可以获取已通过commit提交的索引数据,也可以获取writer新添加的索引数据
记录其中两种编码方式:
VByte格式
变长格式存储
每个byte的高1位:是否有更多byte可读,
每个byte的低7位:具体值,低字节在前,高字节在后
ZigZag整数编码
Protocol Buffers / Thrift 使用
位运算:(i >> 31) ^ (i << 1)
目的:有符号整数映射为无符号
结果:-1 => 1,1 => 2,-2 => 3,2 => 4等
知识点很多,后续要继续结合源码进行学习分析,come on !