主题
十一、ElasticSearch(6 题)
11.1 ★★★★ElasticSearch 的核心概念有哪些?Index、Document、Shard、Replica 之间的关系是怎样的?集群的节点角色是什么?
📖 点击查看答案
核心概念:
- Index:类似数据库的表,是 Document 的逻辑集合。
- Type:7.x 已废弃,6.x 仅支持
_doc。 - Document:最小数据单元,JSON 格式,有唯一
_id。 - Field:文档中的字段,类似数据库列。
- Mapping:定义字段类型(text/keyword/integer/date 等)与分词规则。
- Setting:索引配置(分片数、副本数、刷新间隔等)。
关系层级:
- Cluster → Node → Index → Shard → Segment → Document
- Index 逻辑包含多个 Shard(主分片),每个 Shard 可有 Replica(副本分片)。
- Shard 是 Lucene 实例,包含多个 Segment(不可变段)。
- 一个 Document 属于一个 Shard,由
_routing(默认_idhash)决定。
节点角色:
| 角色 | 职责 | 适用规模 |
|---|---|---|
| Master-eligible | 管理集群元数据、索引创建/删除 | 3 个(防脑裂) |
| Data | 存储 Shard,执行 CRUD/搜索 | 可水平扩展 |
| Coordinating | 路由请求、汇总结果(默认所有节点) | 高查询场景独立部署 |
| Ingest | 文档预处理(pipeline) | 数据清洗 |
| Machine Learning | 异常检测(付费) | 安全监控 |
分片与副本:
- Primary Shard:主分片,索引创建时固定(7.x 默认 1),需 reindex 才能改。
- Replica Shard:副本分片,可动态调整,提供高可用与读扩展。
- 主副本不可在同一节点(保证容灾)。
json
// 索引创建
PUT /orders
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"orderId": { "type": "keyword" },
"amount": { "type": "double" },
"createTime": { "type": "date" }
}
}
}关键点:分片数 = 主分片数 × (1 + 副本数);分片过小限制容量,过大浪费资源(建议单分片 30-50GB);Master 节点不存储数据,专用保证稳定。
11.2 ★★★★倒排索引的原理是什么?Term Dictionary、Posting List 分别起什么作用?为什么要用倒排索引?和 MySQL 正向索引有什么区别?
📖 点击查看答案
倒排索引原理:将文档中的词项(Term)提取出来,建立 Term → 文档 ID 列表的映射,搜索时通过词项直接定位文档,避免全表扫描。
核心组件:
- Term Dictionary:存储所有去重后的词项,按字典序排序,支持二分查找。
- Term Index:Term Dictionary 的索引(FST 数据结构),常驻内存,快速定位 Term 在 Dictionary 中的位置。
- Posting List:每个 Term 对应的文档 ID 列表,存储 doc_id、词频(TF)、位置(Position)、偏移(Offset)。
- Segment:不可变的倒排索引单元,多个 Segment 合并(Merge)提升查询效率。
Posting List 压缩:
- FOR(Frame of Reference):对有序 doc_id 增量编码 + 位压缩。
- Roaring Bitmap:用于 filter 场景的位图压缩,支持高效集合运算(AND/OR)。
为什么用倒排索引:
- 全文检索场景下,正向索引需扫描所有文档匹配关键词,O(N) 复杂度。
- 倒排索引通过词项直接拿到文档列表,O(1) ~ O(log N) 复杂度。
- 支持短语查询(Position)、相关性打分(TF-IDF/BM25)。
与 MySQL 正向索引对比:
| 维度 | MySQL 正向索引 | ES 倒排索引 |
|---|---|---|
| 结构 | B+Tree,行 → 字段值 | Term → Doc 列表 |
| 查询 | 按主键/范围扫描 | 按词项直达文档 |
| 全文搜索 | LIKE '%xxx%' 全表扫 | 分词后查 Term |
| 聚合 | 需扫描所有行 | 直接遍历 Posting List |
| 更新 | 原地更新(B+Tree) | 不可变 Segment + 标记删除 |
| 适用 | 等值/范围查询、事务 | 全文检索、多维聚合 |
示例:
文档1: "Elasticsearch is fast"
文档2: "Elasticsearch is distributed"
Term Dictionary:
distributed → [2]
elasticsearch → [1, 2]
fast → [1]
is → [1, 2]
查询 "elasticsearch fast":
倒排:elasticsearch ∩ fast = {1,2} ∩ {1} = {1}关键点:分词器(Analyzer)决定 Term 的生成;Keyword 类型不分词(整体作为一个 Term),Text 类型分词;MySQL 适合结构化查询,ES 适合非结构化检索。
11.3 ★★★★ElasticSearch 的写入流程是怎样的?数据是如何从 Coordinator Node 到 Primary Shard 再到 Replica Shard 的?近实时搜索的原理(refresh、flush、translog)是什么?
📖 点击查看答案
写入流程:
- Coordinator Node 接收写请求,根据
_routing(默认_id)计算目标 Shard。 - Primary Shard 接收请求,写入 translog + Index Buffer。
- Primary 将请求并行转发给所有 Replica Shard。
- Replica 写入成功后返回 ACK 给 Primary。
- Primary 等待所有 Replica(或
wait_for_active_shards指定数量)ACK 后,返回给 Coordinator。 - Coordinator 返回客户端成功。
近实时搜索原理:
- Index Buffer:写入先进入内存缓冲区,默认 1 秒后触发 refresh。
- refresh:将 Index Buffer 生成新 Segment,写入 OS PageCache(文件系统缓存),此时可被搜索。默认 1 秒,所以称"近实时"。
- translog:写入时同步记录日志(类似 MySQL redo log),保证宕机不丢数据。
index.translog.durability:request(每次请求 fsync,安全但慢)或async(默认 5s fsync)。 - flush:将 PageCache 中的 Segment 刷盘(fsync)+ 清空 translog。默认触发条件:translog 达 512MB 或 30 分钟。
写入 → Index Buffer + translog
↓ refresh(1s)
Segment(PageCache,可搜索)
↓ flush(512MB / 30min)
Segment 持久化到磁盘 + 清空 translogSegment Merge:
- Segment 不可变,频繁 refresh 产生大量小 Segment。
- 后台线程 Merge 小 Segment 为大 Segment,物理删除被标记的文档。
- 减少文件句柄与查询时合并开销。
json
// 调整刷新间隔(写入密集场景)
PUT /orders/_settings
{
"index.refresh_interval": "30s", // 默认 1s
"index.translog.durability": "async",
"index.translog.sync_interval": "30s"
}写一致性与 quorum:
wait_for_active_shards:默认 1(仅 Primary),可设all或quorum。- 写入失败时 Coordinator 会重试到其他 Replica。
关键点:refresh 是"可搜索"的门槛,flush 是"持久化"的门槛;translog 是数据安全兜底;批量写入时增大 refresh_interval 与 buffer 可大幅提升吞吐。
11.4 ★★★ElasticSearch 如何实现深度分页?scroll 和 search_after 的区别是什么?from/size 为什么有性能问题?
📖 点击答案
from/size 的性能问题:
- 分页公式:
from + size,每个 Shard 返回from + size条文档给 Coordinator。 - Coordinator 需对
shard数 × (from + size)条数据排序后取 size 条。 - 默认限制
from + size ≤ 10000,深度分页时内存与 CPU 开销巨大。
三种深度分页方案:
scroll:基于快照,首次查询生成
scroll_id,后续携带scroll_id拉取下一批。- 适合全量导出/重计算,不适合实时查询(快照过期)。
scroll=1m指定快照保留时间,过期自动清理。
search_after:基于排序字段的游标,每次查询携带上一页最后一条的排序值。
- 实时查询,无快照开销,适合用户分页。
- 要求排序字段唯一(建议
_id兜底)。
search_after + PIT:7.10+ 推荐方案,结合 Point In Time 保证数据一致性。
对比:
| 维度 | from/size | scroll | search_after |
|---|---|---|---|
| 场景 | 浅分页 | 全量导出 | 深度分页/实时 |
| 一致性 | 实时 | 快照 | 实时(需排序字段稳定) |
| 性能 | 深度差 | 稳定 | 稳定 |
| 限制 | ≤ 10000 | 需维护 scroll_id | 需排序字段唯一 |
json
// scroll 示例
POST /orders/_search?scroll=1m
{
"size": 100,
"query": { "match_all": {} }
}
// 后续
POST /_search/scroll
{
"scroll": "1m",
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAA..."
}
// search_after 示例
GET /orders/_search
{
"size": 10,
"sort": [
{ "createTime": "desc" },
{ "_id": "asc" } // 兜底保证唯一
]
}
// 下一页
GET /orders/_search
{
"size": 10,
"sort": [{ "createTime": "desc" }, { "_id": "asc" }],
"search_after": ["2024-01-01", "abc123"] // 上一页最后一条的排序值
}关键点:scroll 占用资源大,需及时 clear_scroll 释放;search_after 不能跳页,只能顺序翻;用户分页推荐 search_after,导出场景用 scroll。
11.5 ★★★ElasticSearch 的聚合查询(Aggregation)有哪几种?Bucket、Metric、Pipeline 的区别?聚合查询如何优化?
📖 点击查看答案
聚合类型:
Bucket Aggregation(桶聚合):按规则将文档分组,类似 SQL
GROUP BY。terms:按字段值分桶。range:按数值范围分桶。date_histogram:按时间分桶(如每天/每月)。filter/filters:按条件分桶。
Metric Aggregation(指标聚合):计算数值指标,类似 SQL
SUM/AVG。- 单值:
avg、sum、max、min、cardinality(去重数)、value_count。 - 多值:
stats(一组统计值)、percentiles(分位数)、extended_stats。
- 单值:
Pipeline Aggregation(管道聚合):基于其他聚合结果二次计算。
cumulative_sum:累计求和。derivative:求导(环比)。bucket_sort:桶排序。
json
// 综合示例:按日期分桶 + 每桶求平均金额 + 累计求和
GET /orders/_search
{
"size": 0,
"aggs": {
"daily_sales": {
"date_histogram": {
"field": "createTime",
"calendar_interval": "day"
},
"aggs": {
"avg_amount": { "avg": { "field": "amount" } },
"cumulative": {
"cumulative_sum": { "buckets_path": "avg_amount" }
}
}
}
}
}优化策略:
- 加 query 过滤:聚合前用
query缩小文档集,避免全量扫描。 - 使用 doc_values:默认开启,确保聚合字段非
fielddata(text 类型聚合慢)。 - keyword 而非 text:分桶字段用 keyword 类型,避免分词。
- size 控制:
terms聚合size不要过大,避免桶爆炸。 - partition:terms 桶数过大时使用
include分区聚合。 - 预计算:高频聚合场景用 Transform 或 Rollup 预聚合。
- 冷热分离:历史数据查得少,热数据 SSD 加速。
关键点:聚合在 Coordinator 汇总,深度嵌套聚合会放大请求量;cardinality 用 HyperLogLog 算法,有 1% 误差;fielddata 加载到堆内存,慎用。
11.6 ★★★ElasticSearch 集群的健康状态(Green/Yellow/Red)分别代表什么?集群脑裂问题是什么?如何进行性能优化?
📖 点击查看答案
集群健康状态:
- Green:所有主分片与副本分片都正常分配。
- Yellow:主分片正常,部分副本未分配(如单节点时副本无法分配)。
- Red:部分主分片不可用,数据丢失风险,需立即处理。
脑裂问题:
- 原因:网络分区导致部分 Master-eligible 节点失联,各自选举新 Master,集群分裂为两个独立集群。
- 后果:两个 Master 同时写入,数据不一致,恢复时需手动合并。
- ES 7.x+ 解决方案:
- 自动选举机制:仅当 Master-eligible 节点数 ≥
discovery.zen.minimum_master_nodes(旧版需手动设置)时才能选举。 - 7.x 默认
cluster.initial_master_nodes启动引导,运行时基于投票quorum = (master_eligible / 2) + 1。 - 推荐 3 个专用 Master 节点,避免脑裂。
- 自动选举机制:仅当 Master-eligible 节点数 ≥
性能优化:
写入优化:
- 增大
refresh_interval(如 30s)减少 Segment 数量。 - 批量写入(bulk API),单批 5-15MB。
- 关闭副本先写入,完成后再开(
number_of_replicas: 0)。 translog.durability=async降低 fsync 频率。
- 增大
查询优化:
- 用 filter 替代 query(filter 走缓存,不计算打分)。
- 避免大
from深度分页,用search_after。 - 字段类型合理:精确匹配用 keyword,全文检索用 text。
- 聚合字段开启 doc_values,关闭不需要的
_source。
集群层面:
- 分片容量控制:单分片 30-50GB。
- 节点堆内存 ≤ 32GB(指针压缩),且 ≤ 物理内存 50%。
- 冷热分离:SSD 用于热数据,HDD 用于冷数据。
- 预留磁盘水位:85% 告警,90% 只读,95% 限制分配。
yaml
# elasticsearch.yml 关键配置
cluster.name: my-cluster
node.roles: [data, master] # 7.9+ 角色配置
indices.fielddata.cache.size: 40%
indices.queries.cache.size: 10%
thread_pool.search.queue_size: 1000- JVM 优化:
- 堆内存设为物理内存 50%,不超过 32GB。
- 使用 G1GC(JDK 11+)或 CMS。
- 关闭 swap(
bootstrap.memory_lock: true)。
关键点:监控指标(CPU、堆使用率、GC、查询延迟);慢查询日志开启(indices.search.slowlog);定期 _forcemerge 历史索引(只读后合并为 1 个 Segment)。