1. ES 基础概念
什么是 ES?主要特点是什么?
Elasticsearch 是一个基于 Lucene 的分布式搜索和分析引擎,主要用于全文搜索、结构化搜索、分析等场景。它的主要特点包括:
- 分布式架构:数据可以分布在多个节点上,支持水平扩展和高可用性。
- 实时搜索:能够近乎实时地对数据进行索引和搜索。
- 多种数据类型支持:支持文本、数字、日期、地理位置等多种数据类型的索引和搜索。
- 强大的查询语言:支持复杂的过滤、排序和聚合操作。
- RESTful API:通过 HTTP 接口进行数据索引和查询,使用 JSON 格式进行数据交互。
索引(Index)、文档(Document)、字段(Field)概念
ES关系型数据库的对比记忆:
ElasticSearch | 关系型数据库 |
---|---|
索引(indices) | 数据库(database) |
类型(types) | 表(tables) |
文档(documents) | 行(rows) |
字段(fields) | 列(columns) |
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中包含多个类型(表),每个类型中包含多个文档(行),每个文档中包含多个字段(列)。
几个相关概念:
概念 | 说明 |
---|---|
indices(索引库) | indices是index的复数,代表许多的索引, |
type(类型) | 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念 |
document(文档) | 存入索引库原始的数据。比如每一条商品信息,就是一个文档 |
field(字段) | 文档中的属性 |
mappings(映射配置) | 字段的数据类型、属性、是否索引、是否存储等特性 |
node(节点) | 节点就是es实例 |
cluster(集群) | 一个集群有多个节点 |
shard(分片) | 索引分片,es将一个完整的索引分为多个分片 |
replica(副本) | 每个shard可以设置一定数量的副本,写入的文档同步给副本,副本可以提供查询功能,分摊系统的都负载 |
解释 text
和 keyword
类型的区别
text
类型:用于全文搜索的字段类型,会对文本进行分词处理,并生成倒排索引。适合存储长文本,如邮件内容、产品描述等。text
字段支持模糊查询和全文检索,但不支持排序和聚合操作。keyword
类型:用于精确匹配的字段类型,不会对文本进行分词处理,而是将整个文本作为一个整体进行索引。适合存储结构化数据,如邮箱地址、主机名、状态码等。keyword
字段支持精确匹配、排序和聚合操作。
什么是停顿词过滤?
停顿词过滤是指在文本处理过程中,自动过滤掉某些出现频率较高但对文本内容理解贡献较小的词语,如“的”、“是”、“在”等。停顿词过滤的作用包括:
- 减少存储空间:过滤掉停顿词可以减少索引的大小,节省存储空间。
- 提高搜索效率:减少不必要的词条处理,提高搜索和查询的效率。
- 提升搜索准确性:通过去除无关词条,提升搜索结果的相关性和准确性。
Mapping 是什么?
Mapping 是 Elasticsearch 中用于定义索引结构和字段类型的机制。它类似于关系型数据库中的表结构,包含以下内容:
- 字段类型:如
text
、keyword
、date
、numeric
等。 - 分析器:用于对
text
字段进行分词处理。 - 索引选项:如是否索引、是否存储等。
如何定义 Mapping?
Mapping 可以在创建索引时定义,也可以在索引创建后动态添加。
- 创建索引时定义 Mapping
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text" },
"description": {
"type": "text" },
"price": {
"type": "float" },
"created_at": {
"type": "date" }
}
}
}
- 动态添加 Mapping
PUT /my_index/_mapping
{
"properties": {
"author": {
"type": "keyword" }
}
}
Mapping 的常见配置
- 字段类型:
text
:用于全文搜索,支持分词。keyword
:用于精确匹配,不支持分词。date
:用于日期和时间。numeric
:如integer
、float
等,用于数值计算。
- 分析器:
standard
:默认分析器,支持英文分词。ik_smart
:中文智能分词器。
- 索引选项:
index
:是否索引字段(默认true
)。store
:是否存储字段原始值(默认false
)。
Lucene 基本概念
segment
: lucene 内部的数据是由一个个 segment 组成的,写入 lucene 的数据并不直接落盘,而是先写在内存中,经过了 refresh 间隔,lucene 才将该时间段写入的全部数据 refresh成一个 segment,segment 多了之后会进行 merge 成更大的 segment。lucene查询时会遍历每个 segment 完成。由于 lucene 写入的数据是在内存中完成,所以写入效率非常高。但是也存在丢失数据的风险,所以 ES 基于此现象实现了 translog,只有在 segment 数据落盘后,ES 才会删除对应的 translog。
doc
: doc 表示 lucene 中的一条记录.
field
:field 表示记录中的字段概念,一个 doc 由若干个 field 组成。
term
:term 是 lucene 中索引的最小单位,某个 field 对应的内容如果是全文检索类型,会将内容进行分词,分词的结果就是由 term 组成的。如果是不分词的字段,那么该字段的内容就是一个term。
倒排索引
: lucene索引的通用叫法,即实现了 term
到 doc list
的映射。
正排数据
:搜索引擎的通用叫法,即原始数据,可以理解为一个doc list。
docvalues
:ES 中的列式存储的名称,ES 除了存储原始存储、倒排索引,还存储了一份docvalues,用作分析和排序。
2. 倒排索引
底层
一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
ID | Name | Age | Sex |
---|---|---|---|
1 | Kate | 24 | Female |
2 | John | 24 | Male |
3 | Bill | 29 | Male |
ID是Elasticsearch自建的文档id,那么Elasticsearch建立的索引如下:
Name:
Term | Posting List |
---|---|
Kate | 1 |
John | 2 |
Bill | 3 |
Age:
Term | Posting List |
---|---|
24 | [1, 2] |
29 | 3 |
Sex:
Term | Posting List |
---|---|
Female | 1 |
Male | [2, 4] |
Posting | list |
Elasticsearch分别为每个field都建立了一个倒排索引,一个字段有一个自己的倒排索引。Kate, John, 24, Female这些叫term,而[1,2]就是Posting List。Posting list就是一个int的数组,存储了所有符合某个term的文档id。
ES设计的宗旨:一切设计都是为了提高搜索的性能(另一层意思:为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新)
先来看一个最简单的倒排索引,如下图:
左边是关键词 term 列表
,每个 term
对应包含它的 文档 id 列表
,但是这个效率明显不高,左边 term 列表的时间复杂度是 O(n)
。优化方法: 对 term 列表进行排序后,用二分法查找,如下图:
这样的时间复杂度是 O(logN)
,缺点就是需要对 term 列表 进行排序,但是回想 ES 设计的宗旨,这都是值得的。
排序后的 term 列表称为 Term Dictionary
,文档 id 列表称为 Posting List
。但是 ES 想尽可能地利用内存,因为内存操作时间远少于磁盘操作,那么目前的Term Dictionary
还是太大了,无法放入内存,这只是一个Term Dictionary
,事实上 ES 集群中会有许多个 Term Dictionary
,这些要全部放入内存显然是不现实的,所以 ES 设计了 Term Index
,在保证执行效率的同时,尽量缩减内存空间的占用。
Term Index
大概长这样:
从数据结构上分类 Term Index
算是一个字典树
(但不完全是,后面会详细说)。Term Index不会包含所有的 term,只包含一些 term前缀
。通过 Term Index 可以快速地定位到 Term Dictionary 的某个 offset,然后从这个位置再往后顺序查找。
最终倒排索引的结构图如下:
其中 Term Index
在内存中存储,Term Dictionary
和 Posting List
在磁盘中。一次搜索的步骤就是:
- 搜索 Term Index 树找到对应 Term Dictionary 中的 offset,因为匹配到的可能只是一个前缀,需要再顺序往后找,直到匹配
- 通过 Term Dictionary 找到对应的 Posting List
ES 针对倒排索引做了一个类似两级索引的结构,牺牲了一定的插入/更新性能带来了搜索性能的提升。ES针对还额外做了两点优化:
Term Dictionary
在磁盘上面是分 block(块) 保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去- ·Term Index· 在内存中是以 FST(finite state transducers)的数据结构保存的
什么是FST呢?
这就是为什么上文说到Term Index并不完全是字典树,就是因为它是FST类型。
假设我们现在要将 mop, moth, pop, star, stop, top(term index里的term前缀) 映射到序号:0,1,2,3,4,5 (term dictionary的block位置)。
比如查询 moth,就把 m,o,t,h 路径上的权重相加得到 1 即为 offset 值。
FST 有两个优点:
- 空间占用小:通过对词典中单词前缀和后缀的重复利用,压缩了存储空间
- 查询速度快:O(len(str)) 的查询时间复杂度
这种方式也会导致查找时需要更多的 CPU 资源,但是总得来说,放进内存的收益 > CPU的资源消耗。
小节:
倒排索引是二级索引,先通过 Term Index
(FST数据结构)定位到 offset
,然后再到 Term Dictionary
顺序往后查询到具体的 term
,最后定位到具体的 Posting List
中的 docId
的集合。其中 Term Index
通过压缩技术,用计算换取空间,压缩后放入内存中,减少了查询时间,Term Dictionary
和 Posting List
还是放在磁盘中。
痛点:docId
存储空间太大