1. 什么是Elasticsearch
Elasticsearch 是一个分布式、RESTful 风格的搜索引擎,基于 Apache Lucene。它提供了一个高度可扩展的全文搜索引擎,具备近实时的搜索能力。Elasticsearch 适用于各种用例,包括文档搜索、日志和指标数据分析、应用程序性能监控、线程分析等。
Elasticsearch最根本的使用场景是搜索引擎,比如说我们有一堆文档,我们想要搜索到所有包含"树獭叔叔"的文档,这个时候就可以借助Elasticsearch了。
2. Segment
Segment是Lucene的基本存储单元,Lucene是es依赖的底层开源技术,一个Segment单元由以下四部分组成:
2.1. 倒排索引 Inverted Index
倒排索引是一种用于快速全文检索的数据结构,它将文档和词语进行关联,从而使搜索引擎能够快速查找到包含特定词语的文档。倒排索引逆转了传统索引(如在书本的后面查找关键词)的关系,即它将每个词和包含该词的文档列表关联起来。
假设我们有以下三个文档集:
- "Elasticsearch is a search engine"
- "Elasticsearch is extremely powerful"
- "Search engines are fascinating"
建立倒排索引的步骤如下:
- 分词(Tokenization) :
-
- 第一步是将文档分解为独立的词语。例如:
-
-
- Doc 1: ["Elasticsearch", "is", "a", "search", "engine"]
- Doc 2: ["Elasticsearch", "is", "extremely", "powerful"]
- Doc 3: ["Search", "engines", "are", "fascinating"]
-
- 去重和排序:
-
- 过滤掉重复的词,并对词语进行排序。例如:
-
-
- 总词汇表: ["Elasticsearch", "Search", "a", "are", "engine", "engines", "extremely", "fascinating", "is", "powerful"]
-
- 建立倒排列表:
-
- 对每个词建立一个文档列表,记录包含该词的所有文档。结果如下:
词语 | 文档列表 |
---|---|
Elasticsearch | [1, 2] |
Search | [1, 3] |
a | [1] |
are | [3] |
engine | [1] |
engines | [3] |
extremely | [2] |
fascinating | [3] |
is | [1, 2] |
powerful | [2] |
在Elasticsearch中,倒排索引是Lucene库的一部分。Elasticsearch 使用Lucene来管理和查询这些倒排索引,从而实现高效的全文搜索。下面是如何在Elasticsearch中处理倒排索引的一些关键步骤:
- 文档的分词和分析:
-
- 当文档被索引时,Elasticsearch会先将文档内容进行分词和分析。这通常包括分词、去除停用词(如"a", "is", "the"等)以及将单词归一化(例如将单词转换为小写)。
- 建立倒排索引:
-
- 分词过程生成的词语会被添加到倒排索引中,每个词语关联到包含该词的文档列表中。
- 查询和匹配:
-
- 当进行搜索时,Elasticsearch 会解析查询内容,找到相关的词语,并使用倒排索引快速定位到包含这些词语的文档。然后,根据查询的类型,可能还会计算每个文档的相关度得分,从而排序返回结果。
2.2. Term Index
以上面的倒表索引表为例:
词语(Term) | 文档列表 |
---|---|
Elasticsearch | [1, 2] |
Search | [1, 3] |
a | [1] |
are | [3] |
engine | [1] |
engines | [3] |
extremely | [2] |
fascinating | [3] |
is | [1, 2] |
powerful | [2] |
其实我们不难发现,如果是对文章进行分词,那么词项的数量级是很大的,这个时候如果是通过遍历的方式查找每一个词项,那么他的效率是很低的,因此,我们可以按照字典序对词项进行排序,查找的时候就可以借助二分法优化查询效率。
就算这样,我们在查找时还是会存在问题,因为词项的数据量很大,如果直接把词项表存在内存中,很可能有内容问题,因此,我们可以借助字典数的数据结构维护整个词项表,减少内存占用的同时提高查询效率。
2.3. Doc Values
Doc Values本质是通过冗余存储达到更快的排序与搜索,例如我们可以把商品文档的价格属性进行冗余存储,这样我们在依靠价格进行排序时,就能更快的完成排序与搜索过程。
2.4. Stored Fields
文档数据存储的物理单元。
3. Lucene
前面我们已经介绍了Lucene的基本单元segment,这里就要介绍一下Lucene是如何对segment进行调度了。
前面提到,segment是由多个存储结构共同构成的,如果更新的话,就需要对多个结构进行更新,为了满足高性能读写的需求,我们可以在segment的维度进行读写分离,老segment只负责读,新写入的数据生成新的segment,随着segment越来越多,可以定期对小的segment进行merge,合并成大的segment,这就是Lucene中对segment的基本调度规则。
4. 如何基于Lucene实现高可用
如果大量的数据都写在一个Lucene单元中,必然会导致读写操作的竞争,那么,有什么办法提高奇并发读写的性能吗?当然有,就像数据库的分库分表一样,es也有自己的分库分表:
4.1. Index name
可以根据数据类型对Lucene进行分类,分成多个Index name,根据不同的Index name路由道不同类型的数据。例如,将娱乐文章和学术文章区分开(类似于数据库的垂直分表)。
4.2. Shard分片
可以将同一个Index name中的数据通过hash的方式打散到不同的分片上,之后部署到不同的机器,进一步提升可用性。(类似于水平分表)
我们还可以对shard分片进行主从冗余部署,提高可靠性。
5. ES的结构
5.1.1. Document
ES是面向文档的,索引、检索、排序、过滤的对象都是文档。
它是ES中可搜索的最小单位,使用JSON作为文档的格式。
文档中包含着若干个字段,类似于关系型数据库中的一行数据。
ruby
{
"_index": "user",
//"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 26,
"_primary_term": 4,
"found": true,
"_source": {
"name": "John Doe",
"age": 21,
"birthday": "2000/01/01",
"interests": ["running", "movie"]
}
}
5.1.2. Mapping
文档中的每个字段都有自己的类型,可以是字符串、数值、日期等。通过 Mapping来设定字段的名称和类型, 类似于关系型数据库中的 Schema。比如上面那个文档对应的Mapping 如下:
sql
user {
"mappings": {
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "short"
},
"birthday": {
"type": "date"
},
"interests": {
"type": "string"
}
}
}
}