【Elasticsearch源码解析】从Lucene开始

好久没写Elasticsearch相关的文了,之前写过2篇关于ES的文章,如下:
xx
xx


今天其实不算是写的ES文章,是有关Lucene的。要研究ES的源码,我们先研究一下ES底层用Lucene的原理,就从Lucene最简单的开始吧。

在ES中,我们要做搜索通常比较简单,部署好ES的服务,采用REST接口的方式和ES server通讯。文档的增删改查都采用ES定义好的领域语言,用JSON的方式传输。这样我们不需要知道ES底层是如何工作的(当然要实现不同搜索需要还是要明白一些term,match等等不同的处理方式)。

这里我们就看下,假如不用Elasticsearch实现搜索,而是直接用Lucene做一个搜索引擎,需要有哪些工作要做。

首先,先来了解一下Lucene中的一些核心概念和关键的API。

1、IndexWriter
lucene中最重要的的类之一,它主要是用来将文档加入索引,同时控制索引过程中的一些参数使用。
2、Analyzer
分析器,主要用于分析搜索引擎遇到的各种文本。常用的有StandardAnalyzer分析器,StopAnalyzer分析器,WhitespaceAnalyzer分析器等。
3、Directory
索引存放的位置;lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。
4、Document
文档;Document相当于一个要进行索引的单元,任何可以想要被索引的文件都必须转化为Document对象才能进行索引。
5、Field
类似于数据库中的一个字段,存储了key-value值。
6、IndexSearcher
是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具;
7、Query
Query类似关系型数据库中的SQL语句。与关系型数据库类似,Lucene提供了以下的基本查询:精确查询xxx = ? TermQuery、范围查询 xxx BETWEEN? AND ? PointRangeQuery、模糊查询 xxx LIKE '%?%' PrefixQuery、RegexpQuery、组合查询 (...) AND (...) OR (...) BooleanQuery
8、QueryParser
是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。
9、Hits
在搜索完成之后,需要把搜索结果返回并显示给用户,只有这样才算是完成搜索的目的。在Lucene中,搜索的结果的集合是用Hits类的实例来表示的。

了解了Lucene的这些概念之后,我们尝试自己动手用Lucene实现一个最简单的搜索功能。这里只做最核心的2个功能:1是将一个文档加入索引,2是用个query词从索引检索文档。其他辅助的功能暂时不管。涉及的核心代码如下(这里我们用的是Lucene ):

一、建立索引

// 注:这里Doc是我们自己定义的一个类, 表示文档,包含id,title,content 3个字段
public void indexDocs(List docs, String indexDir) throws Exception {
    long startTime = System.currentTimeMillis();//记录索引开始时间
    Analyzer analyzer = new StandardAnalyzer();
    Directory directory = FSDirectory.open(Paths.get(indexDir));
    IndexWriterConfig config = new IndexWriterConfig(analyzer);

    IndexWriter indexWriter = new IndexWriter(directory, config);

    for (int i = 0; i < docs.size(); i++) {
        Document doc = new Document();
        //添加字段
        doc.add(new IntField("id", docs.get(i).getId(), Field.Store.YES));
        doc.add(new TextField("title", docs.get(i).getTitle(), Field.Store.YES));
        doc.add(new TextField("content", docs.get(i).getContent(), Field.Store.YES));
        indexWriter.addDocument(doc);
    }

    indexWriter.commit();
    System.out.println("共索引了" + indexWriter.numDocs() + "个文件");
    indexWriter.close();
    System.out.println("创建索引所用时间:" + (System.currentTimeMillis() - startTime) + "毫秒");
},

这里涉及到上面说的多个Lucene的核心概念:

StandardAnalyzer:比较常用的一种Analyzer
Directory: 索引保存的位置
IndexWriter: 用于将文档加入索引
Document: 文档, 包含多个Field
IntField, TextField:文档的字段

二、搜索索引

public void doSearch(String indexDir, String queryStr) throws Exception {
    Directory directory = FSDirectory.open(Paths.get(indexDir));
    DirectoryReader reader = DirectoryReader.open(directory);
    IndexSearcher searcher = new IndexSearcher(reader);
    Analyzer analyzer = new StandardAnalyzer();
    QueryParser parser = new QueryParser("content", analyzer);
    Query query = parser.parse(queryStr);

    long startTime = System.currentTimeMillis();
    TopDocs docs = searcher.search(query, 10);

    System.out.println("查找" + queryStr + "所用时间:" + (System.currentTimeMillis() - startTime));
    System.out.println("查询到" + docs.totalHits + "条记录");

    //遍历查询结果
    for (ScoreDoc scoreDoc : docs.scoreDocs) {
        Document doc = searcher.doc(scoreDoc.doc);
        String content = doc.get("content");
        System.out.println(content);
    }
    reader.close();
}

这里又出现了几个关键的概念:

IndexSearcher:用于检索一个索引
Query:表示一个检索
QueryParser:利用Analyzer解析一个用户的输入,生成一个Query
ScoreDoc,Hit:表示命中的文档

调用的逻辑比较简单,如下:

private void indexDocs() throws Exception {
    Doc doc1 = new Doc(1, "java", "hello java");
    Doc doc2 = new Doc(2, "python", "hello python");
    Doc doc3 = new Doc(3, "php", "hello php");
    List docs = new ArrayList();
    docs.add(doc1);
    docs.add(doc2);
    docs.add(doc3);
    docIndexer.indexDocs(docs, indexDir);
}

private void searchDocs() throws Exception {
    System.out.println("==========search 'hello'=============");
    docSearcher.doSearch(indexDir, "hello");

    System.out.println("==========search 'java'=============");
    docSearcher.doSearch(indexDir, "java");
}

至此,一个最简单搜索就实现了,其中涉及了很多Lucene的概念和API,可以看到还是比较容易的,当然这里了只实现了最简单的搜索,要做到ES的分布式、高可靠等特性还是很难的。

后面我们将陆续分析上面代码涉及的各个模块,希望能从底层了解Lucene的工作原理。

下一篇我们将分析Lucene检索中比较核心的Analyzer模块,Analyzer 分词器,实际上就是一个文本的分析过程,或者说是将输入文本转化为文本特征过程。这里我们使用的最简单StandardAnalyzer,实际上Lucene的Analyzer模块有很多实现类,也是关键的扩展点,比如中文常用的IKAnalyzer。

本文涉及到完整代码的github地址如下

[email protected]:guangyuanyu/lucene-demo.git

你可能感兴趣的:(【Elasticsearch源码解析】从Lucene开始)