ElasticSearch
ElasticSearch #
分布式搜索与分析引擎,全文搜索的核心解决方案
📋 目录 #
核心原理 #
倒排索引 #
ES 速度快的秘密
倒排索引结构:
| 词项 (Term) | 文档列表 (Postings) |
|---|---|
| "苹果" | [文档1, 文档2] |
| "水果" | [文档1] |
| "公司" | [文档2] |
| "产品" | [文档2] |
工作流程:
文档1: "苹果是一种水果"
文档2: "苹果公司推出了新产品"
↓ 分词处理
倒排索引:
"苹果" → [文档1, 文档2]
"水果" → [文档1]
"公司" → [文档2]
"产品" → [文档2]
↓ 用户搜索 "苹果"
快速找到: 文档1, 文档2
分布式架构 #
核心概念:
| 概念 | 说明 |
|---|---|
| 分片 (Shard) | 数据水平分片,提升并行度 |
| 副本 (Replica) | 数据冗余,提升可用性和读并发 |
| 协调节点 | 路由请求,聚合结果 |
查询与评分 #
BM25 算法:
score = IDF × (TF / (TF + k1 × ((1 - b) + b × DL/ALG)))
其中:
- IDF: 逆文档频率(词越稀有越重要)
- TF: 词频(词出现次数越多越重要)
- k1, b: 调节参数
- DL: 文档长度
- ALG: 平均文档长度
高可用机制 #
面试高频 ⭐⭐⭐⭐⭐ ES 如何保证高可用?节点挂了怎么办?
副本机制(基础高可用) #
每个主分片可以配置多个副本分片:
副本作用:
- 高可用:主分片挂了,副本提升为主分片继续服务
- 提升读性能:读请求可以负载均衡到主分片和副本分片
配置:
PUT /my_index/_settings
{
"number_of_replicas": 1 // 每个主分片1个副本,总数据量翻倍
}
⚠️ 主分片和副本不会分配在同一个节点上,保证节点宕机副本还在
主节点选举 #
ES 集群通过 ZenDiscovery 模块选主:
选主流程(ES 7.x之前):
- 所有节点互相发现,互相ping
- 得到节点列表,按照节点id排序
- 第一个节点当选为主节点
- 超过半数节点同意当选,完成选举
脑裂问题: 网络分区时,可能选出多个主节点,导致集群分裂,数据不一致。
脑裂解决:
# 旧版配置
discovery.zen.minimum_master_nodes: (总节点数 / 2) + 1
必须获得超过半数节点选票才能当选,所以分区后只有一个分区能选出主节点。
ES 7.x之后改进:
- 基于 Raft 算法 选主,不需要手动配置
minimum_master_nodes - 自动处理脑裂,更可靠
故障转移流程 #
当数据节点宕机:
- 主节点感知到节点失联(ping超时)
- 该节点上的所有主分片,在对应副本节点上提升为主分片
- 集群恢复黄/绿状态,继续服务
当主节点宕机:
- 剩余节点重新选举新主节点
- 新主节点重新分配分片,集群恢复
集群状态说明:
| 状态 | 说明 |
|---|---|
| Green | 所有主分片和副本都正常分配 |
| Yellow | 所有主分片正常分配,有副本未分配(单节点集群总是Yellow) |
| Red | 有主分片未分配,集群部分不可用 |
分片分配感知 #
ES 主节点分配分片时,会感知节点拓扑:
机架 → 节点 → 分片
尽量把主分片和副本分配到不同机架→ 即使整个机架掉电,副本还在其他机架,集群可用。
持久化机制 #
ES 通过 translog 保证数据不丢失:
- 数据写入内存buffer → 写入translog日志
- refresh 生成新segment → 清空buffer
- 如果节点宕机,重启时回放translog恢复内存中未flush的数据
持久化流程:
- translog 同步刷盘,保证数据不丢
- 定期flush将segment写入磁盘
- 合并小segment为大segment
ES 高可用总结 #
| 层面 | 保障机制 |
|---|---|
| 数据层面 | 副本冗余,主分片宕机副本升级 |
| 节点层面 | 自动故障发现,自动故障转移 |
| 选举层面 | 过半机制,防止脑裂 |
| 拓扑层面 | 分片分配感知,跨机架分配 |
| 持久化层面 | translog 日志,宕机可恢复 |
索引设计 #
索引创建 #
PUT /my_index
{
"settings": {
"number_of_shards": 3, // 主分片数(不可修改)
"number_of_replicas": 1, // 副本数(可动态调整)
"refresh_interval": "1s", // 刷新间隔
"index": {
"max_result_window": 10000 // 深分页限制
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word", // 中文分词
"fields": {
"keyword": { // 多字段:精确匹配
"type": "keyword"
}
}
},
"price": { "type": "double" },
"timestamp": { "type": "date" },
"content": {
"type": "text",
"index": false // 不索引(仅存储)
}
}
}
}
动态映射 vs 显式映射 #
| 映射方式 | 优点 | 缺点 |
|---|---|---|
| 动态映射 | 使用方便,自动推断 | 可能类型冲突 |
| 显式映射 | 精确控制,性能更好 | 需要预定义 |
分片设计原则 #
总数据量 / 单分片建议容量(50GB) = 主分片数
例如: 500GB数据 → 10个主分片
注意事项:
- 主分片数创建后不可修改
- 过多分片增加集群开销
- 过少分片限制扩展性
分词器选择 #
面试重点 ⭐⭐⭐⭐
分词器组成 #
分词器 = Character Filters + Tokenizer + Token Filters
字符过滤器 (Character Filters)
↓ 分词前预处理
分词器 (Tokenizer)
↓ 词语切分
词条过滤器 (Token Filters)
↓ 后处理(小写、停用词、同义词等)
常用分词器对比 #
| 分词器 | 适用场景 | 特点 |
|---|---|---|
| Standard | 通用 | 国际化支持好 |
| Simple | 英文 | 非字母字符分隔 |
| Whitespace | 日志分析 | 保留格式 |
| IK Analyzer | 中文 ⭐ | 粗粒度/细粒度 |
| 结巴分词 | 中文 | 未登录词识别弱 |
| Snowball | 英文 | 词干提取 |
| Keyword | 精确匹配 | 不分词 |
IK 分词器 #
# 安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.x.x/elasticsearch-analysis-ik-7.x.x.zip
# 测试
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国"
}
# 输出: ["中华人民共和国", "中华", "人民", "共和国"]
GET /_analyze
{
"analyzer": "ik_smart",
"text": "中华人民共和国"
}
# 输出: ["中华人民共和国"]
同义词配置 #
"settings": {
"analysis": {
"filter": {
"synonym_filter": {
"type": "synonym",
"synonyms_path": "analysis/synonyms.txt"
}
},
"analyzer": {
"my_synonym_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "synonym_filter"]
}
}
}
}
synonyms.txt:
手机, 电话, 移动电话
笔记本电脑, 笔记本, 本本
实战优化 #
性能优化 #
写入优化:
# 1. 使用Bulk API
POST /_bulk
{"index": {"_index": "logs"}}
{"field1": "value1", "field2": "value2"}
{"index": {"_index": "logs"}}
{"field1": "value3", "field2": "value4"}
# 2. 调整刷新间隔(写入高峰期)
PUT /my_index/_settings
{
"index": {
"refresh_interval": "30s" # 默认1s
}
}
# 3. 写入时关闭副本
PUT /my_index/_settings
{
"number_of_replicas": 0
}
查询优化:
// 1. 使用filter代替query(不计算评分)
{
"query": {
"bool": {
"filter": {
"term": { "status": "active" }
}
}
}
}
// 2. 分页限制(避免深分页)
GET /_search
{
"from": 0,
"size": 10,
"query": { ... }
}
// 3. 使用scroll API(大批量)
GET /my_index/_search?scroll=1m
{
"size": 1000,
"query": { ... }
}
索引生命周期管理 (ILM) #
PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "30d"
}
}
},
"warm": {
"min_age": "30d",
"actions": {
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
冷热分离架构 #
🎯 面试题汇总 #
基础 #
- 什么是倒排索引?
- ES为什么搜索快?
- 什么是分片和副本?
- 如何选择分片数量?
分词 #
- 中文分词器有哪些?
- IK分词器的细粒度/智能模式区别?
- 如何配置同义词?
- 分词器组成部分?
优化 #
- 如何提高写入性能?
- 深分页问题及解决方案?
- 如何减少索引大小?
- 什么是ILM?
高级 #
- BM25评分算法?
- 分布式搜索流程?
- ES如何保证高可用?节点挂了怎么办?
- 脑裂问题及解决?
- 集群黄/红状态是什么意思?如何解决?
- 如何实现拼音搜索?
🎯 面试题答案详解 #
基础篇 #
- 什么是倒排索引?
答案:
倒排索引 = 词项 → 文档列表
正排 vs 倒排:
| 类型 | 说明 |
|---|---|
| 正排索引 | 文档 → 词项(已知文档找词) |
| 倒排索引 | 词项 → 文档(已知词找文档)⭐ |
示例:
文档1: "苹果是一种水果"
文档2: "苹果公司推出新产品"
倒排索引:
苹果 → [文档1, 文档2]
水果 → [文档1]
公司 → [文档2]
产品 → [文档2]
- ES为什么搜索快?
答案:
| 技术 | 作用 |
|---|---|
| 倒排索引 | 按词项快速找到文档 |
| 分片并行 | 搜索分散到多个节点并行 |
| Lucene内核 | 优化的索引结构,如FST |
| 缓存机制 | File System Cache |
- 什么是分片和副本?
答案:
| 概念 | 作用 | 说明 |
|---|---|---|
| Shard(分片) | 水平分割数据 | 提高并行度 |
| Replica(副本) | 高可用+读扩展 | 主分片挂了副本升级 |
架构:
- 如何选择分片数量?
答案:
经验公式:
分片数 = 总数据量 / 单分片推荐容量
单分片推荐: 20GB-50GB
太小 → 分片过多,开销大
太大 → 扩容困难
注意:
- 分片数创建后不能修改
- 但可以用 _reindex API重新索引
分词篇 #
- 中文分词器有哪些?
答案:
| 分词器 | 特点 |
|---|---|
| IK Analyzer | 最常用,ik_smart/ik_max_word |
| 结巴分词 | 好用,速度快 |
| HanLP | 功能最全,支持NLP |
| Standard | ES自带,按空格切,中文不行 |
- IK分词器的细粒度/智能模式区别?
答案:
| 模式 | 说明 | 示例: "中华人民共和国" |
|---|---|---|
| ik_max_word | 细粒度,尽可能多切分 | 中华人民共和国,中华,人民,共和国 |
| ik_smart | 智能模式,粗粒度 | 中华人民共和国 |
使用场景:
- 索引时用ik_max_word(分细一点,召回全)
- 搜索时用ik_smart(分粗一点,更准确)
- 如何配置同义词?
答案:
步骤:
- 创建
analysis/synonyms.txt文件
手机,电话,移动电话
笔记本电脑,笔记本,本本
- 索引配置
{
"settings": {
"analysis": {
"filter": {
"synonym_filter": {
"type": "synonym",
"synonyms_path": "analysis/synonyms.txt"
}
}
}
}
}
- 分词器组成部分?
答案:
分词器 = Character Filters + Tokenizer + Token Filters
Character Filters(字符过滤): 过滤HTML、转大写等
Tokenizer(分词): 切分词语
Token Filters(词过滤): 小写、同义词、停用词
优化篇 #
- 如何提高写入性能?
答案:
| 优化项 | 配置 |
|---|---|
| 批量写入 | Bulk API |
| 调大refresh_interval | 默认1s → 30s或-1 |
| 副本数设0 | 写入时设0,写入完恢复 |
| 使用SSD | 磁盘IO快 |
| 异步刷盘 | translog.async |
- 深分页问题及解决方案?
答案:
深分页问题: from = 1000000, size = 10 → 性能极差
解决方案:
| 方案 | 适用场景 |
|---|---|
| Scroll | 遍历大数据(导出) |
| Search After | 分批查询,利用上一页最后一条 |
| 避免深分页 | 产品层面限制页数 |
Search After示例:
{
"size": 10,
"search_after": [last_sort_value], // 上一页最后一条
"sort": [{"id": "asc"}]
}
- 如何减少索引大小?
答案:
| 方法 | 说明 |
|---|---|
| 禁用_source | 不需要完整文档时禁用 |
| 压缩存储 | index.codec: best_compression |
| 字段类型优化 | 用keyword不用text(不需要分词) |
| force_merge | 合并segment,清理删除标记 |
- 什么是ILM?
答案:
ILM = Index Lifecycle Management,索引生命周期管理
四个阶段:
Hot → Warm → Cold → Delete
热 温 冷 删除
配置示例:
{
"policy": {
"hot": {"actions": {"rollover": {"max_size": "50GB", "max_age": "30d"}}},
"warm": {"min_age": "30d", "actions": {"allocate": {"number_of_replicas": 0}, "forcemerge": {"max_num_segments": 1}}},
"cold": {"min_age": "60d", "actions": {"allocate": {"number_of_replicas": 0}}},
"delete": {"min_age": "90d", "actions": {"delete": {}}}
}
}
高级篇 #
- BM25评分算法?
答案:
BM25公式:
score = Σ( IDF * TF / (TF + k1 * (1 - b + b * (fieldLength / avgFieldLength))) )
核心概念:
| 参数 | 含义 |
|---|---|
| IDF | 逆文档频率,词越稀有越重要 |
| TF | 词频,词出现次数越多越重要 |
| fieldLength | 字段长度,越短越重要 |
| k1, b | 调节参数 |
- 分布式搜索流程?
答案:
两个阶段:
- Query Phase: 每个分片返回TopK doc ID
- Fetch Phase: 协调节点合并排序,获取完整文档
- ES如何保证高可用?节点挂了怎么办?
答案:
高可用机制:
| 机制 | 作用 |
|---|---|
| 副本机制 | 主分片挂了副本升级 |
| 自动分片重分配 | 节点挂了自动把分片移到其他节点 |
| 脑裂预防 | discovery.zen.minimum_master_nodes |
节点挂了流程:
1. 主节点检测到某节点失联
2. 该节点上的主分片 → 选一个副本升级为主
3. 重新分配缺失的副本
4. 集群恢复Green/Yellow状态
- 脑裂问题及解决?
答案:
脑裂: 网络分区时,多个节点认为自己是主节点
解决方法:
| ES版本 | 配置 |
|---|---|
| 7.x前 | discovery.zen.minimum_master_nodes: (n/2)+1 |
| 7.x+ | 自动,无需配置 |
原理:必须获得超过半数节点同意才能成为主
- 集群黄/红状态是什么意思?如何解决?
答案:
| 状态 | 说明 |
|---|---|
| Green | 所有主分片和副本都正常 ✓ |
| Yellow | 所有主分片正常,有副本异常 |
| Red | 有主分片异常! |
Yellow解决:
GET /_cat/indices?v
# 查看哪些索引副本分配失败
# 增加节点或调整副本数
PUT /index/_settings
{"number_of_replicas": 0}
Red解决:
# 紧急!检查主分片为什么分配失败
GET /_cluster/allocation/explain
# 可能原因: 节点不够、磁盘满、分片丢失
- 如何实现拼音搜索?
答案:
方案1: pinyin分词器
{
"settings": {
"analysis": {
"analyzer": {
"pinyin_analyzer": {
"tokenizer": "pinyin_tokenizer"
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"pinyin": {
"type": "text",
"analyzer": "pinyin_analyzer"
}
}
}
}
}
}
搜索:
{
"query": {
"multi_match": {
"query": "zhongguo",
"fields": ["name", "name.pinyin"]
}
}
}
🔗 相关笔记 #
- MySQL
- 消息队列
- 分布式系统
最后更新: 2026-04-29