【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<Doc> 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<Doc> docs = new ArrayList<Doc>();
    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地址如下

git@github.com:guangyuanyu/lucene-demo.git

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,540评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,640评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,643评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,672评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,525评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,387评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,809评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,450评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,743评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,787评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,560评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,406评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,824评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,048评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,335评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,784评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,997评论 2 337