🔍Elasticsearch全文搜索
I. 什么是倒排索引(Inverted Index)?
核心概念: 将“文档➡️单词”的映射转换为“单词➡️ 文档列表”的映射。
Term Dictionary(词项字典): 记录所有不重复的单词,通常使用 FST(Finite State Transducers)结构压缩存储在内存中,查询速度极快。
Posting List(倒排列表): 记录单词对应的文档 ID 集合(以及词频、位置等信息)。通过 Frame of Reference (FOR) 和 Roaring Bitmap 进行压缩。
作用: 实现全文检索的关键,能快速找到包含特定关键词的所有文档。
II. ES 的写入流程是怎样的?
宏观流程: 客户端 ➡️ 协调节点 -> 主分片(Primary Shard)➡️副本分片(Replica Shard)。
微观流程(数据持久化):
Buffer & Translog: 数据先写入内存 Buffer,同时追加到 Translog(事务日志,防止断电丢失)。
Refresh(默认1秒): Buffer 中的数据生成 Segment 文件写入 Filesystem Cache(系统缓存),此时数据可被搜索,但未落盘。Buffer 清空。
Flush(默认30分钟或Translog满): 触发 Commit 操作,强制将 Filesystem Cache 中的 Segment 刷入磁盘,清空 Translog。
III. ES 的读取/搜索流程(Query Then Fetch)?
搜索分为两个阶段:
Query 阶段:
协调节点将请求广播到所有相关的分片(主或副)。
每个分片在本地查询,返回文档 ID 和 打分(Score) 给协调节点。
协调节点进行全局排序和合并。
Fetch 阶段:
协调节点根据确定的文档 ID,向对应的分片发送请求抓取实际的文档内容(_source)。
分片返回数据,协调节点拼装最终结果返回给客户端。
IV. 什么是深度分页(Deep Pagination)?如何解决?
问题: 使用 from + size 分页时,若请求第 10000 页(每页10条),每个分片都需要查出前 100010 条数据,协调节点要汇总 shards * 100010 条数据排序,最后只取 10 条。这会导致大量的 CPU 和内存消耗(OOM)。
解决方案:
Scroll(游标): 创建快照,适合全量数据导出,不适合实时查询(因为无法获取新写入的数据)。
Search After: 基于上一页最后一条数据的排序值(Sort values)来查询下一页。性能好,适合无限下拉加载,但不支持随机跳转页码。
V. ES 如何解决“脑裂”问题?
现象: 集群因网络波动分裂,出现两个主节点(Master),导致数据不一致。
解决方案:
核心原则: Quorum(法定票数)。
配置: 设置 discovery.zen.minimum_master_nodes = N/2 + 1(其中 N 是有资格成为 Master 的节点数)。即只有获得过半节点支持才能当选 Master。
- 注:ES 7.0 以后引入了新的集群协调子系统,移除该配置,系统会自动处理,但在设计集群拓扑时仍需注意节点规划。
VI. ES 性能调优有哪些常见手段?
写入优化:
使用 Bulk 请求: 批量写入。
加大 Refresh Interval: 默认1秒,导入大量数据时可改为 -1 或 30s,减少 Segment 合并压力。
使用自动生成的 ID: 避免检查 ID 冲突。
初次导入禁用 Replicas: 导入完成后再开启副本。
查询优化:
使用 Filter 代替 Query: Filter 不计算分数且支持缓存。
路由(Routing): 写入和查询时指定路由键,避免扫描所有分片。
硬件层面: 使用 SSD,且给 Filesystem Cache 预留一半内存(ES 堆内存建议不要超过 32GB)。
避免大宽表: 减少 text 字段,不需要分词的字段使用 keyword。
VII. ES 更新和删除文档的原理?
ES 的 Segment 文件是不可修改的。
删除: 不会物理删除,只是在 .del 文件中将该文档标记为“deleted”。搜索时会过滤掉这些文档。
更新: 本质是 Delete + Insert。先将旧文档标记删除,再写入新文档。
物理清理: 在 Segment Merge(段合并)阶段,才会真正物理移除被标记删除的数据。
VIII. ES的刷新延时配置是什么?
即Elasticsearch中的 refresh_interval配置。
1. 这个配置是什么?
参数名: index.refresh_interval
默认值: 1s(1秒)
含义: 数据写入 ES 后,多久可以被搜索到。
核心概念: 这就是为什么 ES 被称为 “近实时”(Near Real-Time, NRT) 搜索引擎的原因。数据写进去了,但默认要过 1 秒才能查出来。
2. 底层原理(简述)
写入 Buffer: 数据先写入内存缓冲区(Memory Buffer)。
Refresh(刷新): 每隔 refresh_interval(默认1秒),ES 会把缓冲区的数据“刷”到文件系统缓存(FileSystem Cache)中,生成一个新的 Segment。
可见性: 一旦生成 Segment,数据就可以被搜索到了(虽然此时还没彻底落盘到硬盘,但在缓存里已可见)。
3. 考察点
海量数据写入性能优化
问题所在: 默认 1秒一次 Refresh,如果正在进行大量数据写入(比如批量导入 1 亿条日志),频繁生成小的 Segment 会消耗大量 CPU 和 I/O 资源,严重拖慢写入速度。
优化方案: 在批量写入期间,将 refresh_interval 设置为 -1(关闭刷新)或者设置得很大(比如 30s)。
好处: 减少 Segment 的生成频率,大幅提升写入吞吐量。
代价: 在这期间写入的数据无法被立即搜索到(等批量写完再改回 1s)。