一文带你了解ElashticSerch倒排索引设计原理

0 阅读7分钟

在工作中可能遇到过数据库查询慢的问题,这时候引入ES(Elasticsearch)作为搜索引擎择可以极大的提升查询效率。这就离不开ES的核心设计:倒排索引。通过本文带你快速了解ES的使用及倒排索引的设计

  1. 快速的全文搜索能力

    • ES 的核心卖点之一是高效的全文检索。要在海量文档中快速定位包含某些词语的文本片段,传统的顺序匹配效率低下。
    • 由此引出的问题:如何在海量文本中快速找到包含某些关键词的文档?需要一种高效的索引结构来实现“关键词 → 文档”的快速映射。
  2. 对相关性的高效排序与评分

    • ES 不仅要找出匹配的文档,还要对匹配度进行排序(如 TF-IDF、BM25 等)。这要求索引结构不仅记录文档中的词语位置,还能支持相关性计算时的快速聚合。
    • 设计思路自然指向需要对文档集合进行去重、聚合和统计的机制。
  3. 对复杂查询的支持(布尔、短语、通配符、范围等)

    • ES 支持多种查询类型组合,如 MUST、SHOULD、FILTER 等组合查询,以及短语查询、前缀查询等。
    • 这促使索引结构不仅要记录词语的出现,还要高效地处理词序、位置、权重等信息,以支持组合条件的快速交集/并集运算。
  4. 分布式与水平可扩展性

    • ES 天生的分片、副本机制使得检索任务能够分布在多台机器上执行。
    • 这要求索引在设计上具备分布式特性,便于将倒排索引切分、聚合与路由到不同分片进行并行处理。
  5. 低延迟的查询响应和实时性

    虽然严格意义上全文索引通常是靠预计算结构,但 ES 追求接近实时的域值更新与查询响应。 这促使索引在写入时需要高效的更新路径,以及查询时能快速命中相关的倒排结构。

一、核心思想

  • 倒排索引(Inverted Index)将“文档集合中的词项”映射到“包含该词项的文档集合”上。与正向索引(逐文档存字段)不同,倒排索引方便对任意词项快速定位包含该词的文档。
  • ES 在 Lucene 的基础上扩展了分布式、分片、副本、聚合式查询等特性,使得大规模文本检索、实时分析成为可能。

二、数据结构与组织

  1. 文档与字段
  • 文档(Document)是最小的检索单位。每个文档由一个或多个字段(Field)组成。
  • 字段有不同的类型(text、keyword、numeric、date、boolean、ip 等),不同类型会决定索引和分析方式。
  1. 字段类型与索引策略
  • text 字段:用于全文检索,会经过分析器(Analyzer)进行分词、去停用词、同义词扩展等处理。最终形成倒排索引。
  • keyword 字段:作为不分词的精确值,适合聚合、排序、过滤(term-level 过滤)。通常不分析。
  • 数值、日期、布尔等字段:可以实现范围查询、排序、聚合,通常以点值(doc values)或字段缓存形式参与聚合和排序。
  1. 分词与分析链
  • 分词器(Tokenizer)将文本拆分成词项
  • 过滤器(Filters)对分词结果做进一步处理,如小写化、同义词扩展、词干提取、去除停用词等
  • 分析链决定了文本在索引阶段与查询阶段的对比方式,确保查询词与索引中的词项可匹配
  1. 倒排索引的结构
  • 词项字典(Terms Dictionary):记录所有出现的词项及其元信息(如 term frequency、文档频率等)。

  • 倒排表(Postings List):每个词项对应的文档集合及在文档中的位置、字段、单词的位置信息等。常见的信息包括:

    • document id(docID)
    • term frequency(tf,某词在该文档中出现频次)
    • 位置信息(positions,用于短语查询、邻接查询)等
    • 可能还有 payload(附加信息,如位点元数据)
  1. 分片与分布
  • ES 将索引分为多个分片(primary shard,主分片)以实现并行和扩展性。每个分片内部维护一个独立的 inverted index。
  • 副本分片(replica shard)用于高可用和查询并发,副本也包含倒排索引的拷贝。
  • 查询时,ES 可以对相关分片并行执行,汇总结果。

三、写入流程(索引构建)

  • 文档通过 API 索引到一个具体的索引/分片。
  • 文档被分解为字段,文本字段进入分析器链,生成倒排索引的词项和 postings。
  • 操作通常是“near real-time”风格,文档进入一个 translog(事务日志)以确保持久化和恢复;在刷新(refresh)时,最近的分片会将内存中的倒排索引可见并写入段(segment)。
  • 段(Segment)是一个不可变的索引片段,包含自己的倒排结构、文档字段数据等。新文档进入新段,段之间会进行合并(merge)以控制段数量和碎片。

四、查询流程

  • 查询请求被路由到相关索引的相关分片。
  • 分片在倒排索引中匹配词项,生成候选文档集合(docs)、可能的短语查询、前缀查询、范围查询等。
  • 评分(_tf-idf、BM25 等)与查询重排序:ES 使用 Lucene 的 BM25 作为默认评分模型,并支持自定义权重、函数得分、协同过滤等。
  • 聚合、排序、分页等后续结果处理在分片内完成,再进行全局汇总。

五、存储与性能相关的关键点

  • 文档值(Doc Values):用于高效的排序、聚合和脚本化访问,通常以列式存储在磁盘,适合大规模聚合场景。对 keyword、字段类型等配置有影响。

  • 反向索引与正向字段存储的权衡:全文字段通常需要较大倒排索引,而对非文本字段的聚合/过滤,优先使用 doc_values。

  • 刷新与合并策略:

    • 刷新(refresh)将内存中的新文档暴露给搜索接口,带来轻微延迟与增加 I/O。
    • 合并(merge)会将小段合并为大段,影响写入性能与搜索性能之间的折中。
  • 分片数与副本数:影响并发度、查询吞吐、故障恢复能力。过多分片会增加开销,过少可能成为瓶颈。

  • 映射(Mapping)设计:明确字段类型和分析器,避免动态映射过度扩张,确保查询和聚合性能。

六、可扩展性与容错

  • ES 的分布式设计支持水平扩展:增加分片/节点来提升吞吐和容量。
  • 副本机制提供高可用性与只读并发能力。
  • 灾难恢复通过快照(Snapshot)/恢复(Restore)实现跨集群备份。

七、常见优化策略

  • 仅对需要全文检索的字段使用 text,其他字段使用 keyword + doc_values。
  • 对热字段设置合适的分片数量,避免过多小碎片影响查询性能。
  • 使用合适的 analyzer,避免不必要的同义词/形态学处理导致大量词项膨胀。
  • 启用 doc_values(默认对大多数字段开启)以提升聚合与排序性能。
  • 调整 BM25 参数(k1、b)以匹配文本分布和查询需求。
  • 对高基数字段谨慎使用 keyword 类型进行聚合,必要时采用 datastream/聚合策略分段处理。
  • 使用缓存(filter cache、query cache)及线性化分页策略(search after、scroll)来优化大结果集查询。
  • 监控并优化段合并策略,避免频繁合并导致写放大和 CPU 突增。

八、实际设计建议

  • 明确需求:是“全词匹配、分词检索”还是“精确匹配、聚合分析”,从而决定 text/keyword 的使用。
  • 映射设计要点:将需要聚合或排序的字段配置为 keyword + doc_values,文本字段配置为 text(带分析器)。
  • 分片/副本配置:根据数据量、并发访问和可用性需求设定合适的分片数和副本数。
  • 分析器选择:标准分析器(standard)常用,但对语言有特殊需求时选择相应的语言分析器或自定义分词器。
  • 监控与调优:关注查询的峰值延迟、分片级别的合并活动、段的数量与大小,以及缓存命中率。