近期使用了ELK系列组件,不由的感慨其功能的丰富和强大,伴随而来的就是另一个想法“ELK太重,用在简单的小型项目上有些鸡肋,能不能做一个轻量版的搜索引擎用于微小型项目呢?”。
下面将会有一系列的文章来描述整个过程,每篇不会太长,但每篇相比前篇都会有所改进。
1.Lucene.Net简介
做搜索引擎必然绕不开Lucene。Lucene是Apache基金会的开源项目。无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。Elasticsearch同样也是基于Lucene的开源搜索引擎。Lucene.NET则是Lucene的.NET版本。
GitHub:Lucene.NET
1.1.基本概念
- IndexWriter(索引操作器)
lucene中最重要的的类之一,它主要是用来将文档加入索引,同时控制索引过程中的一些参数使用。但不要被Writer这个叫法所迷惑,索引的删除和更新也是由它来实现的。
- Analyzer(分析器)
主要用于分析搜索引擎遇到的各种文本。常用的有StandardAnalyzer分析器、StopAnalyzer分析器、WhitespaceAnalyzer分析器等。同时也可以通过集成接口来实现自定义的分析器,自带的分析器对于中文来讲确实不太友好。
- Directory(索引存放位置)
lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。
- Document(文档)
Document相当于一个要进行索引的单元,任何可以想要被索引的文件都必须转化为Document对象才能进行索引。
- Field(域)
类似于数据库中的一个字段,存储了key-value值。Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。
- IndexSearcher(索引检索器)
是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具;
- Query(查询对象)
Query类似关系型数据库中的SQL语句。与关系型数据库类似,Lucene提供了以下的基本查询:精确查询xxx = ? TermQuery、范围查询 xxx BETWEEN? AND ? PointRangeQuery、模糊查询 xxx LIKE ‘%?%’ PrefixQuery、RegexpQuery、组合查询 (…) AND (…) OR (…) BooleanQuery
- QueryParser(查询生成器)
是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。同时还提供了MultiFieldQueryParser多条件的查询生成器。
- Hits(命中/匹配结果)
在搜索完成之后,需要把搜索结果返回并显示给用户,只有这样才算是完成搜索的目的。在Lucene中,搜索的结果的集合是用Hits类的实例来表示的。
1.2.工作原理
Lucene的工作流程大体上可分为三个部分:索引部分、分词部分、搜索部分。
源字符串首先经过analyzer处理,包括:分词,分成一个个单词(可同时去除stopword)。 将源中需要的信息加入Document的各个Field中,并把需要索引的Field索引起来,把需要存储的Field存储起来。 将索引写入存储器,存储器可以是内存或磁盘。
用户提供搜索关键词,经过analyzer处理。根据查询条件生成查询对象,然后搜索索引找出对应的Document。 用户根据需要从找到的Document中提取需要的Field。最后将命中的结果以Hits的形式返回。
由于Lucene只提供了最基础的功能,结合其提供的框架功能,在每个流程节点都可以根据需要进行组合、扩展、实现,进而构建成功能丰富可用的搜索引擎。
2.简单示例
2.1.安装Lucene.NET
目前正式发行版是3.X,最新的4.8.x只有预发行版。
2.2.创建索引
/// <summary>
/// 创建索引
/// </summary>
/// <param name="fields">Field域集合,也可以是其他结构</param>
/// <param name="route"></param>
public void CreateIndex(Dictionary<string, string> fields, string route)
{
//创建索引操作器配置项,包含Lucene版本定义和Analyzer分析器定义。此处使用4.8版本和标准分析器。
IndexWriterConfig config = new IndexWriterConfig(LuceneVersion.LUCENE_48, new StandardAnalyzer(LuceneVersion.LUCENE_48));
//定义索引存储路径。此处使用磁盘文件存储,存储位置为项目运行目录下的“Lucene”
Lucene.Net.Store.Directory dir = new NIOFSDirectory("Lucene/"+route);
//实例化索引操作器。此处构造函数为上面定义索引存储路径和配置项。
using (IndexWriter writer = new IndexWriter(dir, config))
{
//创建文档
Document doc = new Document();
foreach (var field in fields)
{
//创建数据域。此处使用了StringField域。此外还能用Int32Field、DoubleField等。
Field f = new StringField(field.Key, field.Value, Field.Store.YES);
//添加数据域至文档
doc.Add(f);
}
//索引操作器增加文档
writer.AddDocument(doc);
//刷新索引
writer.Flush(true, true);
}
}
2.3.查询
/// <summary>
/// 关键词查询
/// </summary>
/// <param name="keyword">关键词</param>
/// <param name="field">域</param>
/// <returns></returns>
public Dictionary<Document,float> SearchIndex(string keyword,string field)
{
//定义返回数据结构
Dictionary<Document, float> dic = new Dictionary<Document, float>();
//打开索引存储路径
Lucene.Net.Store.Directory dir = FSDirectory.Open("Lucene");
//实例化索引读取器
using (Lucene.Net.Index.DirectoryReader reader = DirectoryReader.Open(dir))
{
//实例化索引检索器
IndexSearcher searcher = new IndexSearcher(reader);
//创建查询生成器。此处使用了基础的查询生成器QueryPaser,构造函数参数为版本、查询的域、分析器。
//也可以使用MultiFieldQueryPaser多域查询生成器。
QueryParser parser = new QueryParser(LuceneVersion.LUCENE_48, field,
new StandardAnalyzer(LuceneVersion.LUCENE_48));
//生成查询对象。
Query query = parser.Parse(keyword);
//检索并返回结果。检索条件为返回符合程度最高的前10条。
TopDocs matches = searcher.Search(query, 10);
//遍历检索结构,构造需要返回的数据结构
foreach (var match in matches.ScoreDocs)
{
//获取匹配结果中的文档
Document doc = searcher.Doc(match.Doc);
//获取匹配文档的分数
dic.Add(doc,match.Score);
}
}
return dic;
}
2.4.测试示例
//添加索引示例
public void AddDemo()
{
SearchManager searcher = new SearchManager();
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("Name", "南京工大建设监理咨询有限公司");
dic.Add("Address", "南京市中山北路200号");
searcher.CreateIndex(dic, "data");
}
//查询示例
public List<string> SearchDemo(string key,string field)
{
SearchManager searcher = new SearchManager();
List<string> list = new List<string>();
Dictionary<Document, float> docs = searcher.SearchIndex(key, field);
foreach (var doc in docs)
{
string tmp = string.Format("doc:{0},score:{1}", doc.Key.ToString(), doc.Value.ToString());
list.Add(tmp);
}
return list;
}
以上就是一个简单的示例,用来说明Lucene.NET的索引创建及检索过程。后续会在这个基础之上进行不断完善。
不积跬步无以至千里,不积小流无以致江海。