Elasticsearch 常见面试题总结
大部分项目都会用到 Elasticsearch ,面试难免会被问到。于是,利用春节时间简单总结了一下 Elasticsearch 常见问题,希望对球友们有帮助。
少部分内容参考了 Elasticsearch 官方文档的描述,在此说明一下。
Elasticsearch 基础
Elasticsearch 是什么?
ElasticSearch 是一个开源的 分布式、RESTful 搜索和分析引擎,可以用来解决使用数据库进行模糊搜索时存在的性能问题,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。
ElasticSearch 使用 Java 语言开发,基于 Lucence。ES 早期版本需要 JDK,在 7.X 版本后已经集成了 JDK,已无需第三方依赖。
Github 地址:https://github.com/elastic/elasticsearch 。
Lucene 是什么?
Lucene 是一个 Java 语言编写的高性能、全功能的文本搜索引擎库,提供强大的索引和搜索功能,以及拼写检查、高亮显示和高级分析功能。
如果我们直接基于 Lucene 开发,会非常复杂。并且,Lucene 并没有分布式以及高可用的解决方案。像 ElasticSearch 就是基于 Lucene 开发的,封装了许多 Lucene 底层功能,提供了简单易用的 RestFul API 接口和多种语言的客户端,开箱即用,自带分布 式以及高可用的解决方案。
Github 地址:https://github.com/apache/lucene
Elasticsearch 可以帮助我们做什么?
举几个常见的例子:
- 实现各种网站的关键词检索功能,比如电商网站的商品检索、维基百科的词条搜索、Github 的项目检索;
- 本地生活类 APP 比如美团基于你的定位实现附近的一些美食或者娱乐项目的推荐;
- 结合 Elasticsearch、Kibana、Beats 和 Logstash 这些 Elastic Stack 的组件实现一个功能完善的日志系统。
- 使用 Elasticsearch 作为地理信息系统 (GIS) 管理、集成和分析空间信息。
- ......
电商网站检索:
ELK 日志采集系统架构(负责日志的搜索):
为什么需要 Elasticsearch?MySQL 不行吗?
正是谓术业有专攻!Elasticsearch 主要为系统提供搜索功能, MySQL 这类传统关系型数据库主要为系统提供数据存储功能。
MySQL 虽然也能提供简单的搜索功能,但是搜索并不是它擅长的领域。
我们可以从下面两个方面来看:
1)传统关系型数据库的痛点:
- 传统关系型数据库(如 MySQL )在大数据量下查询效率低下, 模糊匹配有可能导致全表扫描。
- MySQL 全文索引只支持
CHAR
,VARCHAR
或者TEXT
字段类型,不支持分词器。
2)Elasticsearch 的优势 :
- 支持多种数据类型,非结构化,数值,地理信息。
- 简单的 RESTful API,天生的兼容多语言开发。
- 提供更丰富的分词器,支持热点词汇查询。
- 近实时查询,Elasticsearch 每隔 1s 把数据存储至系统缓存中,且使用倒排索引提高检索效率。
- 支持相关性搜索,可以根据条件对结果进行打分。
- 天然分布式存储,使用分片支持更大的数据量。
Elasticsearch 中的基本概念
- Index(索引) : 作为名词理解的话,索引是一类拥有相似特征的文档的集合比如商品索引、商家索引、订单索引,有点类似于 MySQL 中的 Table(表)。作为动词理解的话,索引就是将一份文档保存在一个索引中。
- Document(文档) :可搜索最小单位,用于存储数据。索引中的每一条数据叫作一个文档,一般为 JSON 格式,有点类似于 MySQL 中的 Row(行)。文档由一个或者多个字段(Field)组成,字段类型可以是布尔,数值,字符串、二进制、日期等数据类型。
- Type(字段类型) : 每个文档在 ES 中都必须设定它的类型。ES 7.0 之前,一个 Index 可以有多个 Type。6.0 开始,Type 已经被 Deprecated。7.0 开始,一个索引只能创建一个 Type :
_doc
。8.0 之后,Type 被完全删除,解决了多 Type 索引带来的资源浪费、字段冲突、查询效率低下等问题,移除的具体的原因看这里:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/removal-of-types.html 。 - Mapping(映射) :定义字段名称、数据类型、优化信息(比如是否索引)、分词器,有点类似于数据库中的表结构定义(Schema)。6.x 及更早版本中,一个 Index 对应多个 Mapping。7.x 开始,一个 Index 对应一个 Mapping。
- Node(节点) : 相当于一个 ES 实例,多个节点构成一个集群。
- Cluster(集群) :多个 ES 节点的集合,用于解决单个节点无法处理的搜索需求和数据存储需求。
- Shard(分片): Index(索引)被分为多个碎片存储在不同的 Node 节点上的分片中,以提高性能和吞吐量。
- Replica(副本) :Index 副本,每个 Index 有一个或多个副本,以提高拓展功能和吞吐量。
- DSL(查询语言) :基于 JSON 的查询语言,类似于 SQL 语句。
Document(文档)相比较于关系型数据库的 Row(行),有什么优势?
- 更高的自由度:文档可以灵活地新增或删除字段,多个文档之间也不要求字 段完全一致。这允许你在不同文档中存储不同的结构,免去了关系型数据库中必须严格定义表结构的限制。
- 部分结构化存储:Elasticsearch 的文档虽然不要求严格的 Schema,但仍然以 JSON 格式存储,具有一定的结构性。与完全抛弃数据结构化的 K-V 非关系型数据库相比,文档型数据库能更好地支持复杂查询(如范围查询、全文检索等)和索引。
- 层次化结构: 文档可以嵌套子文档,形成层次化的数据结构,方便存储和查询复杂数据关系。
MySQL 与 Elasticsearch 的概念简单类比:
MySQL | Elasticsearch |
---|---|
Table(表) | Index(索引) |
Row(行) | Document(文档) |
Column(列) | Field(字段) |
Schema(约束) | Mapping(映射) |
SQL(查询语言) | DSL(查询语言) |
倒排索引和正排索引
倒排索引是什么?
倒排索引 也被称作反向索引(inverted index),是用于提高数据检索速度的一种数据结构,空间消耗比较大。倒排索引首先将检索文档进行分 词得到多个词语/词条,然后将词语和文档 ID 建立关联,从而提高检索效率。
分词就是对一段文本,通过规则或者算法分出多个词,每个词作为搜索的最细粒度一个个单字或者单词。分词的目的主要是为了搜索,尤其在数据量大的情况下,分词的实现可以快速、高效的筛选出相关性高的文档内容。
如下图所示,倒排索引使用 词语/词条(Term) 来作为索引关键字,并同时记录了哪些 文档(Document) 中有这个词语。
- 文档(Document) :用来搜索的数据,其中的每一条数据就是一个文档。例如一个商品信息、商家信息、一页网页的内容。
- 词语/词条(Term) :对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如
''数据库索引可以大幅提高查询速度"
这段话被中文分词器 IK Analyzer 细粒度分词后得到[数据库,索引,可以,大幅,提高,查询,速度]
。 - 词典(Term Dictionary) :Term 的集合。
Lucene 就是基于倒排索引来做的全文检索,并且 ElasticSearch 还对倒排索引做了进一步优化。
倒排索引的创建和检索流程了解么?
这里只是简单介绍一下倒排索引的创建和检索流程,实际应用中,远比下面介绍的复杂,不过,大体原理还是一样的。
倒排索引创建流程:
- 建立文档列表,每个文档都有一个唯一的文档 ID 与之对应。
- 通过分词器对文档进行分词,生成类似于
<词语,文档ID>
的一组组数据。 - 将词语作为索引关键字,记录下词语和文档的对应关系,也就是哪些文档中包含了该词语。
这里可以记录更多信息比如词语的位置、词语出现的频率,这样可以方便高亮显示以及对搜索结果进行排序(后文会介绍到)。
Lucene 的倒排索引大致是下面这样的(图源:https://segmentfault.com/a/1190000037658997):
倒排索引检索流程:
- 根据分词查找对应文档 ID
- 根据文档 ID 找到文档
倒排索引由什么组成?
- 单词字典 :用于存储单词列表。一般用 B+Tree 或 Hash 拉链法存储,提高查询效率。
- 倒排列表 :记录单词对应的文档集合。分为:
DocID
:即文档 idTF
: 单词出现频率,简称词频Position
:单词在文档中出现的位置,用于检索Offset
:偏移量,记录单词开始结束位置,用于高亮显示
正排索引呢?
不同于倒排索引,正排索引将文档 ID 和分词建立关联。
根据词语查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词语,查询效率较低。
倒排索引和正排索引的区别是什么?
正排索引:
- 优点:维护成本低,新增数据的时候,只要在末尾新增一个 ID
- 缺点:以
DocID
为索引,查询时需要扫描所有词语,一个一个比较,直至查到关键词,查询效率较低。
倒排索引:
- 优点:建立分词和
DocID
关系,大大提高查询效率 - 缺点:建立倒排索引的成本高。并且,维护起来也比较麻烦,因为文档的每次更新都意味着倒排索引的重建。还有一些搜索精度的问题,比如搜索
dogs
和dog
想要相同匹配结果,这时就需要合适的分词器了
Elasticsearch 可以针对某些字段不做索引吗?
文档会被序列化为字段组成的 JSON 格式保存在 ES 中。我们可以针对某些字段不做索引。
这样可以节省存储空间,但是,同时也会让字段无法被搜索。
分词器(Analyzer)
Analyzer 翻译成中文叫做分析器,不过,很多人一般习惯称呼其为分词器。
分词器有什么用?
分词器是搜索引擎的一个核心组件,负责对文档内容进行分词(在 ES 里面被称为 Analysis),也就是将一个文档转换成 单词词典(Term Dictionary) 。单词词典是由文档中出现过的所有单词构成的字符串集合。为了满足不同的分词需求,分词器有很多种,不同的分词器分词逻辑可能会不一样。
常用分词器有哪些?
非中文分词器:
- Standard Analyzer:标准分词器,也 是默认分词器, 英文转换成小写, 中文只支持单字切分。
- Simple Analyzer:简单分词器,通过非字母字符来分割文本信息,英文大写转小写,非英文不进行分词。
- Stop Analyzer :在
SimpleAnalyzer
基础上去除 the,a,is 等词,也就是加入了停用词。 - Whitespace Analyzer : 空格分词器,通过空格来分割文本信息,非英文不进行分词。
上面这些也都是 ES 内置的分词器,详细介绍请看官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html。
这个官方文档为每一个分词器都列举了对应的例子帮助理解,比如 Standard Analyzer 的例子是下面这样的。
- 输入文本内容:
"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
- 分词结果:
[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog's, bone ]
中文分词器:
- IK Analyzer(推荐): 最常用的开源中文分词器,Github 地址:https://github.com/medcl/elasticsearch-analysis-ik,包括两种分词模式:
- ik_max_word:细粒度切分模式,会将文本做最细粒度的拆分,尽可能多的拆分出词语 。
- ik_smart:智能模式,会做最粗粒度的拆分,已被分出的词语将不会再次被其它词语占有。
- Ansj :基于 n-Gram+CRF+HMM 的中文分词的 Java 实现,分词速度达到每秒钟大约 200 万字左右(mac air 下测试),准确率能达到 96%以上。实现了中文分词、中文姓名识别、用户自定义词典、关键字提取、自动摘要、关键字标记等功能。Github 地址:https://github.com/NLPchina/ansj_seg 。
- ICU Analyzer:提供 Unicode 支持,更好地支持亚洲语言。
- THULAC(THU Lexical Analyzer for Chinese) : 清华大学推出的一套中文词法分析工具包,具有中文分词和词性标注功能。Github 地址:https://github.com/thunlp/THULAC-Python 。
- Jcseg :基于 mmseg 算法的一个轻量级中文分词器,同时集成了关键字提取,关键短语提取,关键句子提取和文章自动摘要等功能。Gitee 地址:https://gitee.com/lionsoul/jcseg 。
IK Analyzer 分词示例:
- 输入文本内容:
"数据库索引可以大幅提高查询速度"
- 分词结果:
- 细粒度切分模式:
[数据库,索引,可以,大幅,提高,查询,速度]
- 智能模式:
[数据库,数据,索引,可以,大幅,提高,查询,速度]
- 细粒度切分模式:
其他分词器 :
- Keyword Analyzer :关键词分词器,输入文本等于输出文本。
- Fingerprint Analyzer :指纹分析仪分词器,通过创建标记进行检测。
上面这两个也是 ES 内置的分词器。
Keyword Analyzer 分词示例:
- 输入文本内容:
"The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
- 分词结果:
[ The 2 QUICK Brown-Foxes jumped over the lazy dog's bone. ]
分词器由什么组成?
分析器由三种组件组成:
- Charater Filters:处理原始文本,例如去除 HTMl 标签。
- Tokenizer:按分词器规则切分单词。
- Token Filters:对切分后的单词加工,包括转小写,切除停用词,添加近义词
三者顺序:Character Filters —> Tokenizer —> Token Filter
三者个数:CharFilters(0 个或多个) + Tokenizer(一个) + TokenFilters(0 个或多个)
下图是默认分词器 Standard Analyzer 的分词流程。
Elasticsearch 如何基于拼音搜索?
对于中文内容来说,我们经常需要基于拼音来进行搜索。
在 Elasticsearch 中如何来实现基于拼音来搜索的呢? 我们可以使用 拼音分词器 ,拼音分词器用于汉字和拼音之间的转换,集成了 NLP 工具(https://github.com/NLPchina/nlp-lang),Github 地址:https://github.com/medcl/elasticsearch-analysis-pinyin。
数据类型
Elasticsearch 常见的数据类型有哪些?
常见类型:
- 关键词:
keyword
、constant_keyword
,和wildcard
- 数值型:
long
,integer
,short
,byte
,double
,float
,half_float
,scaled_float
- 布尔型:
boolean
- 日期型:
date
,date_nanos
- 二进制:
binary
结构化数据类型:
- 范围型:
integer_range
,float_range
,long_range
,double_range
,date_range
- ip 地址类型 :
ip
- 软件版本 :
version
文字搜索类型:
- 非结构化文本 :
text
- 包含特殊标记的文本:
annotated-text
- 自动完成建议:
completion
对象和关系类型:
- 嵌套类型:
nested
、join
- 对象类型 :
object
、flattened
空间类型:
- 地理坐标类型 :
geo_point
- 地理形状类型 :
geo_shape
Elasticsearch 官方文档中有详细介绍到各个数据类型的使用:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html 。
keyword 和 text 有什么区别?
keyword
不走分词器,而 text
会走分词器,使用 keyword
关键字查询效率更高,一般在 fields
中定义keyword
类型字段
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword"
}
}
}
Elasticsearch 是否有数组类型?
在 Elasticsearch 中,没有专门的数组数据类型。默认情况下,任何字段都可以包含零个或多个值,但是,数组中的所有值必须具有相 同的数据类型。
Elasticsearch 怎么修改索引字段类型?
可以在 Mapping 中直接修改字段类型吗?
不可以!Elasticsearch 中的 Mapping 有点类似于数据库中的表结构定义,Mapping 中的字段类型只能增加不能修改,否则只能 reindex
重新索引或者重新进行数据建模并导入数据。
什么是 Nested 数据类型?有什么用?
Elasticsearch 官方文档是这样介绍 Nested 数据类型的:
The
nested
type is a specialised version of the[object](https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html)
data type that allows arrays of objects to be indexed in a way that they can be queried independently of each other.Nested (嵌套)类型是对象数据类型的特殊版本,它允许对象数组以一种可以相互独立查询的方式进行索引。
Nested 数据类型可以避免 数组扁平化处理,多个数组的字段会做一个笛卡尔积,导致查询出不存在的数据。
// 会导致查询John White也会匹配,将类型改为nested问题解决
PUT my_index/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
如果您需要索引对象数组并保持数组中每个对象的独立性,请使用 Nested 数据类型而不是对象数据类型。
将多个字段值合并为一个字段怎么做?
使用 copy_to
,比如将 first_name 和 last_name 合并为 full_name ,但 full_name 不在查询结果中展示
PUT my_index
{
"mappings": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}
Mapping
什么是 Mapping?
Mapping(映射)定义字段名称、数据类型、优化信息(比如是否索引)、分词器,有点类似于数据库中的表结构定义。一个 Index 对应一个 Mapping。
Mapping 分为动态 Mapping 和显示 Mapping 两种:
- 动态 Mapping:根据待索引数据自动建立索引、自动定义映射类型。
- 显示 Mapping:手动控制字段的存储和索引方式比如哪些字符串字段应被视为全文字段。
// 显示映射创建索引
PUT /my-index-000001
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
动态 Mapping 使用起来比较简单,在初学 Elasticsearch 的时候可以使用。实际项目中,应该尽量手动定义映射关系。
为什么插入数据不用指定 Mapping?
因为在写入文档时,如果索引不存在,Elasticsearch 会自动根据数据类型 自动推断 Mapping 信息 (Dynamic Mapping),但有时候不是很准确。