Skip to content

🔍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)。
微观流程(数据持久化):

  1. Buffer & Translog: 数据先写入内存 Buffer,同时追加到 Translog(事务日志,防止断电丢失)。

  2. Refresh(默认1秒): Buffer 中的数据生成 Segment 文件写入 Filesystem Cache(系统缓存),此时数据可被搜索,但未落盘。Buffer 清空。

  3. Flush(默认30分钟或Translog满): 触发 Commit 操作,强制将 Filesystem Cache 中的 Segment 刷入磁盘,清空 Translog。

III. ES 的读取/搜索流程(Query Then Fetch)?

搜索分为两个阶段:

  1. Query 阶段:

    • 协调节点将请求广播到所有相关的分片(主或副)。

    • 每个分片在本地查询,返回文档 ID 和 打分(Score) 给协调节点。

    • 协调节点进行全局排序和合并。

  2. Fetch 阶段:

    • 协调节点根据确定的文档 ID,向对应的分片发送请求抓取实际的文档内容(_source)。

    • 分片返回数据,协调节点拼装最终结果返回给客户端。

IV. 什么是深度分页(Deep Pagination)?如何解决?

问题: 使用 from + size 分页时,若请求第 10000 页(每页10条),每个分片都需要查出前 100010 条数据,协调节点要汇总 shards * 100010 条数据排序,最后只取 10 条。这会导致大量的 CPU 和内存消耗(OOM)。
解决方案:

  1. Scroll(游标): 创建快照,适合全量数据导出,不适合实时查询(因为无法获取新写入的数据)。

  2. 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. 底层原理(简述)
  1. 写入 Buffer: 数据先写入内存缓冲区(Memory Buffer)。

  2. Refresh(刷新): 每隔 refresh_interval(默认1秒),ES 会把缓冲区的数据“刷”到文件系统缓存(FileSystem Cache)中,生成一个新的 Segment

  3. 可见性: 一旦生成 Segment,数据就可以被搜索到了(虽然此时还没彻底落盘到硬盘,但在缓存里已可见)。

3. 考察点

海量数据写入性能优化

  • 问题所在: 默认 1秒一次 Refresh,如果正在进行大量数据写入(比如批量导入 1 亿条日志),频繁生成小的 Segment 会消耗大量 CPU 和 I/O 资源,严重拖慢写入速度。

  • 优化方案: 在批量写入期间,将 refresh_interval 设置为 -1(关闭刷新)或者设置得很大(比如 30s)。

    • 好处: 减少 Segment 的生成频率,大幅提升写入吞吐量。

    • 代价: 在这期间写入的数据无法被立即搜索到(等批量写完再改回 1s)。