ElasticSearch
yuankaiqiang Lv5

面试题

一、基础概念类

1. 什么是 Elasticsearch?它的主要用途是什么?

答:
Elasticsearch 是一个基于 Lucene 的开源分布式搜索和分析引擎,支持实时全文检索、结构化搜索、日志分析、监控数据等场景。

主要用途:

  • 全文搜索引擎(如电商商品搜索)
  • 日志和指标分析(结合 Logstash、Kibana 构成 ELK 栈)
  • 实时数据分析平台
  • 数据聚合与可视化

2. Elasticsearch 是如何实现近实时搜索的?

答:
Elasticsearch 默认每 1 秒刷新一次(refresh interval),将内存中的文档写入倒排索引,使其可被搜索,因此被称为“近实时”(NRT, Near Real-Time)。

  • 新文档先写入内存 buffer 和 translog(事务日志)
  • 每隔 1 秒执行一次 refresh,生成新的 segment 并打开供搜索
  • 可通过 ?refresh 参数手动触发刷新
  • 注意:refresh 不等于持久化,数据仍需通过 flush 写入磁盘

3. Elasticsearch 中的倒排索引来是什么?它有什么优势?

答:
倒排索引(Inverted Index)是 Elasticsearch 实现快速全文搜索的核心数据结构。

结构示例:

1
2
3
词项 → 包含该词的文档列表
"apple" → [doc1, doc3]
"banana" → [doc2, doc3]

优势:

  • 快速定位包含某个关键词的文档
  • 支持布尔查询、短语匹配、高亮等功能
  • 相比正向索引(文档 → 词项),更适合全文检索

4. Elasticsearch 是分布式的,它的分布式架构是怎样的?

答:
Elasticsearch 的分布式架构包括以下几个核心概念:

组件说明
Node(节点)一个运行中的 ES 实例
Cluster(集群)由一个或多个节点组成,共同管理数据
Index(索引)类似数据库中的“表”,存储一类文档
Shard(分片)索引被分为多个 shard,实现水平扩展
Replica(副本)每个 shard 可有多个副本,提高容错性和吞吐量
  • 主分片(Primary Shard):负责写操作
  • 副本分片(Replica Shard):用于读操作和故障恢复
  • 分布式查询通过协调节点(Coordinating Node)完成

二、核心机制

5. Elasticsearch 写入数据的流程是怎样的?

答:
写入流程(Index API)如下:

  1. 客户端发送写请求到任意节点(协调节点)
  2. 协调节点根据 _id 计算目标分片:shard = hash(_id) % primary_shard_count
  3. 请求转发到主分片所在节点
  4. 主分片将数据写入内存 buffer 并追加到 translog
  5. 执行 refresh(默认 1s 一次),使文档可被搜索
  6. 定期执行 flush,将内存 buffer 中的数据写入磁盘 segment,并清空 translog
  7. 主分片通知副本分片同步数据,完成后返回客户端成功

✅ 成功条件:主分片 + 所有副本分片都成功写入(取决于 wait_for_active_shards 设置)

6. 什么是 translog?它的作用是什么?

答:
translog(Transaction Log)是一个事务日志,记录了所有对索引的写操作(index、update、delete)。

作用:

  • 保证数据不丢失:即使数据还在内存中未刷新到 segment,崩溃后可通过 translog 恢复
  • 支持实时 get 操作:可通过 translog 获取最新版本的文档
  • flush 操作会清空 translog(或生成新的)

默认每 5 秒或每次 index.refresh_interval 后 fsync 到磁盘(可配置)

7. 说说 refresh、flush、merge 的区别?

操作说明触发频率
refresh将内存 buffer 中的数据生成新的 segment,使其可被搜索默认 1s 一次
flush将内存中的 segment 写入磁盘,并清空 translog默认 30 分钟或 translog 太大时
merge合并多个小 segment 为大 segment,减少文件数,提升性能后台自动进行

merge 是 I/O 密集型操作,但不阻塞读写

8. Elasticsearch 如何保证高可用?

答:
通过以下机制实现高可用:

  • 副本机制(Replica):每个主分片可有多个副本,当主分片宕机时,副本可提升为主分片
  • 集群自动发现与选举:使用 Zen Discovery 或基于 Raft 的新协调机制(7.x+)选举 master 节点
  • 数据分片与负载均衡:数据分散在多个节点上,避免单点故障
  • 脑裂防护:通过设置 discovery.zen.minimum_master_nodes(旧版本)或 cluster.initial_master_nodes(新版本)防止脑裂

三、查询与性能优化

9. 介绍一下常用的查询类型?

答:
常见查询类型包括:

查询类型示例用途
match{"match": {"title": "elastic"}}全文匹配,会分词
term{"term": {"status": "active"}}精确匹配,不分词
match_phrase"quick brown fox"短语匹配,顺序一致
boolmust,should,must_not组合多个条件
range{"age": {"gte": 18}}范围查询
wildcard{"name": {"wildcard": "abc*"}}通配符查询(性能较差)
prefix自动前缀匹配适合补全场景

⚠️ term 查询用于 keyword 类型;match 用于 text 类型

10. 什么是相关性评分(_score)?它是如何计算的?

答:
_score 表示文档与查询的相关性得分,基于 TF-IDFBM25 算法(ES 5.0+ 默认使用 BM25)。

BM25 公式简化理解:

  • 词频(TF)越高,得分越高
  • 逆文档频率(IDF)越高(词越稀有),得分越高
  • 字段长度越短,得分越高

可通过 explain=true 查看评分细节

11. 如何优化 Elasticsearch 的搜索性能?

答:
常见优化手段:

  1. 合理设计 Mapping:
    • 避免 dynamic mapping 导致字段类型错误
    • 使用 keyword 替代 text 用于聚合和精确查询
  2. 调整分片数量:
    • 单个分片建议 10–50GB,避免过多分片
  3. 使用 filter 上下文:
    • filter 不计算评分,可缓存,性能更高
  4. 避免 deep paging:
    • 使用 search_afterscroll 替代 from + size
  5. 冷热架构分离:
    • 热节点处理写入和实时查询,冷节点存储历史数据
  6. 开启慢查询日志:
    • 分析并优化慢查询
  7. 使用预聚合或物化视图(8.x 支持)

四、运维与实战类

12. 如何设计一个适合电商商品搜索的索引结构?

答:
示例 mapping:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PUT /product_index
{
"mappings": {
"properties": {
"id": { "type": "keyword" },
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"category": { "type": "keyword" },
"price": { "type": "float" },
"brand": { "type": "keyword" },
"tags": { "type": "keyword" },
"sales_count": { "type": "integer" },
"on_sale": { "type": "boolean" }
}
}
}

搜索示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /product_index/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
],
"filter": [
{ "term": { "on_sale": true } },
{ "range": { "price": { "gte": 1000 } } }
]
}
}
}

使用 IK 分词器支持中文分词,filter 提升性能

13. Elasticsearch 出现“Too Many Requests”或“Bulk Rejection”怎么办?

答:
这是线程池拒绝请求的常见问题,原因和解决方案如下:

原因:

  • 写入或查询请求过多,超出线程池处理能力
  • 系统资源不足(CPU、内存、磁盘 IO)

解决方案:

  1. 增加线程池队列大小(谨慎):

    1
    2
    3
    thread_pool:
    write:
    queue_size: 1000
  2. 降低 bulk 写入速率,增加 --bulk-size 间隔

  3. 增加节点或分片,分散负载

  4. 升级硬件配置

  5. 使用 retry_on_conflict 和指数退避重试机制

14. 如何安全地升级 Elasticsearch 集群?

答:
推荐滚动升级(Rolling Upgrade)流程:

  1. 查看官方兼容性文档(如 7.x → 8.x 可能需停机)

  2. 备份数据(快照)

  3. 关闭 shard 分配:

    1
    2
    PUT /_cluster/settings
    { "transient": { "cluster.routing.allocation.enable": "primaries" } }
  4. 逐个停止节点,升级软件,重启

  5. 重新启用分配:

    1
    2
    PUT /_cluster/settings
    { "transient": { "cluster.routing.allocation.enable": "all" } }
  6. 验证集群健康状态:GET /_cluster/health

⚠️ 跨大版本升级可能需要重新索引

15. Elasticsearch 中的脑裂问题是什么?如何避免?

答:
脑裂(Split Brain):网络分区导致多个 master 节点同时存在,造成数据不一致。

避免方法:

  • 设置正确的 master 节点选举最小数量:

    1
    2
    3
    4
    5
    # 旧版本(7.x 之前)
    discovery.zen.minimum_master_nodes: 3 # 对于 5 节点集群

    # 新版本(7.x+)使用 quorum-based coordination
    cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
  • 建议奇数个 master-eligible 节点(3、5、7)

  • 配置合理的 discovery.seed_hostscluster.name

五、附加题(高级)

16. 说说 Elasticsearch 中的聚合(Aggregation)类型?

答:
三大类聚合:

  1. Metric Aggregation

    (指标聚合):

    • avg, sum, min, max, cardinality(去重统计)
  2. Bucket Aggregation

    (桶聚合):

    • terms:按字段值分桶
    • range:按范围分桶
    • date_histogram:按时间间隔分桶
  3. Pipeline Aggregation

    (管道聚合):

    • 对其他聚合结果进行二次计算,如 derivative, moving_avg

示例:统计各品类销售额

1
2
3
4
5
6
7
8
"aggs": {
"sales_by_category": {
"terms": { "field": "category" },
"aggs": {
"total_sales": { "sum": "price" }
}
}
}

17. Elasticsearch 支持哪些分词器?如何自定义?

答:
常见分词器:

分词器说明
standard默认,按词边界分词
whitespace按空格分
keyword不分词,整个字段作为一个 term
simple按非字母字符分
language支持中文、英文等语言(如english
ik(第三方)中文分词,支持ik_smartik_max_word

自定义 analyzer 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_ik_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word"
}
}
}
}
}

需提前安装 ik 分词插件:./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/...

18. Elasticsearch 和数据库(如 MySQL)有什么区别?

对比项ELASTICSEARCHMYSQL
存储模型倒排索引 + 正排索引B+ 树索引
查询类型全文搜索、模糊匹配精确查询、JOIN
一致性最终一致性强一致性
写入性能高(批量写入)相对较低
适用场景搜索、日志分析事务处理、关系数据

建议:ES 不替代数据库,而是作为查询加速层

实操

1、安装ElasticSearch

Ubuntu18.04中安装参考 [^1]

ubuntu使用命令行apt安装,mac下载解压使用,下面示例主要使用的是mac系统下面的es版本7.12.0

1
2
3
4
5
6
7
8
9
10
11
安装jdk
sudo apt update
sudo apt install apt-transport-https
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
sudo sh -c 'echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" > /etc/apt/sources.list.d/elastic-7.x.list'

sudo systemctl enable elasticsearch.service (设置开机自启动)
sudo systemctl start elasticsearch.service (启动)
sudo systemctl stop elasticsearch.service (停止)
sudo systemctl restart elasticsearch.service (重启)
sudo systemctl status elasticsearch.service (查看状态)

使用 curl -X GET “localhost:9200/“ 测试是否启动成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name" : "Mac",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "Gz-LMjbISM-8uWPiHC3fug",
"version" : {
"number" : "7.12.0",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "78722783c38caa25a70982b5b042074cde5d3b3a",
"build_date" : "2021-03-18T06:17:15.410153305Z",
"build_snapshot" : false,
"lucene_version" : "8.8.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}

若在服务器中启动失败,可能为内存太小,修改配置文件**/etc/elasticsearch/jvm.options **

1
2
3
4
## -Xms4g
## -Xmx4g
-Xms258m
-Xmx258m

配置远程连接,则设置elasticsearch.yml 文件

1
2
3
4
#设置为0.0.0.0
network.host: 0.0.0.0
#取消注释
cluster.initial_master_nodes: ["node-1", "node-2"]

2、ES可视化elasticsearch-head

elasticsearch-head Gtihub地址 [^2]

1
2
3
#进入到项目目录
npm install
npm run start

连接时遇到跨域问题

image

解决:修改elasticsearch.yml配置文件,增加下面跨域配置后重新启动

1
2
http.cors.enabled: true
http.cors.allow-origin: "*"

3、使用Kibana(版本需与ES一致)

Kibana下载地址 [^3]

解压后需要先启动es再启动kibana,默认端口为5601

设置中文,修改kibana.yml配置文件

1
i18n.locale: "zh-CN"

4、使用ik分词器(版本需与ES一致)

iK分词器下载地址 [^4]

ik提供的两个算法:

  • ik_smart:最少切分
  • ik_max_word:最细粒度划分

下载解压后放入到es的plugins中,并重命名文件夹名称

image

测试查看已有的插件

1

重新启动es与kibana,从kibana中测试ik分词器,字典中查找

ik_smart:最少切分

2

ik_max_word:最细粒度划分

3

未自定义之前

image

在ik配置文件中添加自己自定义的dic文件

image

如果需要自定义的分词,多个自定义的dic文件使用 “**;”** 间隔

asd

修改完后重新启动

image

变成自定义的单词

image

5、索引文档操作

创建索引PUT

不推荐使用类型名,换成 “_doc” 使用

1
2
3
4
5
6
7
8
9
PUT /索引名/类型名(后期可不写)/id
{
请求体
}
PUT /test1/type1/1
{
"name": "袁凯强",
"age": 18
}

image

创建完成后

1

ES的类型可以有多个

  • 字符串类型:textkeyword
  • 数值类型:long、integer、short、byte、double、float、half float、scaled float
  • 日期类型:date
  • 布尔类型:boolean
  • 二进制类型:binary

指定字段的类型

image

获取索引或文档GET

通过GET请求获取具体的信息,后面跟索引名或者是id名都可以

1
2
3
4
5
6
#简单查询
GET test1
GET /test1/_doc/1
#条件查询
GET /test1/_search?q=name:袁
GET /test1/_search?q=name:袁凯强1

1

通过GET _cat/xxx 命令可以获取系统中的其它信息

2

修改(两种方式)

  1. 直接通过 PUT 方式进行修改

    1
    2
    3
    4
    5
    PUT /test1/type1/1
    {
    "name": "袁凯强1",
    "age": 21
    }

    image

  2. 通过 POST 方式进行修改(推荐使用

    1
    2
    3
    4
    5
    6
    7
    POST /test1/type1/1/_update
    {
    "doc": {
    "name": "袁凯强12",
    "age":99
    }
    }

    image

删除DELETE

1
DELETE test2

1

复杂操作搜索select(排序、分页、高亮、模糊查询、精准查询

1
2
3
4
5
6
7
8
GET /test1/_search
{
"query": {
"match": {
"name": "袁"
}
}
}

image

  1. 查询:query

    1
    2
    3
    4
    5
    6
    7
    8
    GET /test1/_search
    {
    "query": {
    "match": {
    "name": "袁"
    }
    }
    }

    image

  2. 结果过滤 :_source

    1
    "_source": ["name"]

    image

  3. 排序:sort(根据数值类型进行排序)

    1
    2
    3
    4
    5
    6
    7
    "sort": [
    {
    "age": {
    "order": "desc"
    }
    }
    ]

    image

  4. 分页:from、size

    1
    2
    "from": 0,
    "size": 1

    image

  5. 布尔值查询:bool

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "bool": {
    "must": [
    {
    "match": {
    "name": "袁凯强"
    }
    },
    {
    "match": {
    "age": 21
    }
    }
    ]
    }

    must 相当于mysql中条件中的and

    image

    should 相当于mysql中条件中的or

    image

    must_not 相当于mysql中条件中的not

    1

  6. 过滤:filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "filter": [
    {
    "range": {
    "age": {
    "gte": 20
    }
    }
    }
    ]

    eq相等,ne、neq不相等,gt大于,lt小于,gte、ge大于等于,lte、le 小于等于,not非,mod求模,div by是否能被某数整除,even是否为偶数,odd是否为奇

    2

  7. 匹配多个条件进行查询match

    1
    2
    3
    4
    5
    "query": {
    "match": {
    "name": "袁 张"
    }
    }

    3

  8. 精确查询:termterm查询是直接通过倒排索引指定的词条进程精确查找的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    PUT test2
    {
    "mappings": {
    "properties": {
    "name": {
    "type": "text"
    },
    "desc": {
    "type": "keyword"
    }
    }
    }
    }
    PUT test2/_doc/1
    {
    "name": "袁凯强hello",
    "desc": "注释"
    }
    PUT test2/_doc/2
    {
    "name": "袁凯强hello",
    "desc": "注释2"
    }
    GET _analyze
    {
    "analyzer": "keyword",
    "text": "袁凯强Hello"
    }
    GET _analyze
    {
    "analyzer": "standard",
    "text": "袁凯强Hello"
    }
    GET /test2/_search
    {
    "query": {
    "term": {
    "name": "袁"
    }
    }
    }
    GET /test2/_search
    {
    "query": {
    "term": {
    "desc": "注释"
    }
    }
    }

    使用es默认的分词器keyword与standard

    keyword不会被拆分

    111

    standard会被拆分

    222

    因为name与desc中的字段类型不一样,keyword不能被分词器解析

    444

    name:text类型(trem+text分词查询=》通过standard解析)

    image

    desc:keyword类型(trem+keyword精确查询=》通过keyword解析)

    1

    多个值匹配精确查询

    2

  9. 高亮查询:highlight

1
2
3
4
5
"highlight":{
"fields":{
"name":{}
}
}

image

自定义高亮的标签,默认为em

1

6、集成SpringBoot

API官方文档 [^5]

API在线文档 [^6]

222

333

maven依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

使用的对象

123

导入依赖时,保证es版本与系统版本的一致

image

精确查找时,不想被分词器解析

  1. 名称上加上keyword:keyword参考 [^7]

1

  1. 定义的时候加上定义的类型

    @field注解参考 [^8]

    @Document注解参考 [^9]

1
2
3
4
5
6
7
@Field(type=FieldType.Text, analyzer="ik_max_word")     表示该字段是一个文本,并作最大程度拆分,默认建立索引
@Field(type=FieldType.Text,index=false) 表示该字段是一个文本,不建立索引
@Field(type=FieldType.Date) 表示该字段是一个文本,日期类型,默认不建立索引
@Field(type=FieldType.Long) 表示该字段是一个长整型,默认建立索引
@Field(type=FieldType.Keyword) 表示该字段内容是一个文本并作为一个整体不可分,默认建立索引
@Field(type=FieldType.Float) 表示该字段内容是一个浮点类型并作为一个整体不可分,默认建立索引
date 、float、long都是不能够被拆分的

222

使用类似于JPA的形式使用elasticsearchanalyzer和search_analyzer的区别

Elasticsearch中analyzer和search_analyzer的区别 [^10]

1
2
3
analyzer和search_analyzer的区别
在创建索引,指定analyzer,ES在创建时会先检查是否设置了analyzer字段,如果没定义就用ES预设的
在查询时,指定search_analyzer,ES查询时会先检查是否设置了search_analyzer字段,如果没有设置,还会去检查创建索引时是否指定了analyzer,还是没有还设置才会去使用ES预设的

使用类似于JPA的形式使用elasticsearch

 评论