当前位置: 首页 > news >正文

Elasticsearch-4--倒排索引的原理?

1、什么是倒排索引?

1、与“正排索引”对比

(1)、正排索引 (Forward Index)

定义:以文档为单位组织数据。记录每个文档中包含了哪些词语。
映射方向:文档(Document) → 词语列表(Term List)。(如数据库的行式存储)。
示例:Doc1 → [“lucene”, “search”, “engine”]
特点:

  • 结构直观,类似于文档的原始内容表示。
  • 在搜索引擎中,通常作为构建倒排索引的中间步骤。
  • 查询效率低:当需要查找包含某个特定词(如 “search”)的所有文档时,必须遍历所有文档的词语列表,时间复杂度高。
(2)、倒排索引 (Inverted Index)

定义:以词语为单位组织数据。记录每个词语出现在哪些文档中。
映射方向:词语(Term) → 包含该词的文档列表(Posting List)。(如搜索引擎的列式存储)。
示例:“search” → [Doc1, Doc3, Doc5]
特点:

  • 是现代搜索引擎(如 Elasticsearch, Lucene)的核心数据结构。
  • 查询效率极高:对于关键词搜索,可以直接通过词语定位到所有相关文档列表,无需扫描全部文档。
  • 通常还会在文档列表中存储额外信息,如词频(TF)、位置(Position)、偏移量(Offset)等,用于相关性评分和高亮显示。
(3)、对比

在这里插入图片描述
总结:
正排索引是基础,倒排索引才是实现高效全文检索的基石。

2、倒排索引的核心组成

一个完整的倒排索引由两部分构成。

1、词典(Term Dictionary)

存储所有唯一的词项(Term),并按字典序排列。

  • 存储所有唯一的词项(Terms)
  • 通常用有序数组、Trie、FST(Finite State Transducer)实现
  • 支持快速查找:"lucene"是否存在?位置在哪?

2、倒排表(Posting List)

每个词项对应的文档列表,记录包含该词项的文档ID、词频(Term Frequency)、位置(Position)等信息。

每个词项对应一个倒排表,记录:

  • 文档ID(DocID)
  • 词频(TF, Term Frequency)
  • 位置(Position) → 用于短语查询"big data"
  • 偏移(Offset) → 用于高亮显示
  • Payload → 自定义数据(如权重、标签)

3、构建倒排索引步骤示例

假设有3个文档。
在这里插入图片描述

(1)、分词与处理(Analyzer)

Doc1: [lucene, search, engine]
Doc2: [elasticsearch, uses, lucene]
Doc3: [search, engine, lucene]
一般分词器会去掉停用词如:“is”, “a”, “with”

(2)、构建词典和倒排表

在这里插入图片描述
说明:

  • Term: 词项(词语)。
  • DocFreq: 文档频率(Document Frequency),表示包含该词项的文档数量。
  • Posting List: 倒排列表,包含每个包含该词项的文档信息。
    • 格式 [DocID:TF:Positions] 中:
      • DocID: 文档ID。
      • TF (Term Frequency): 该词项在对应文档中的出现次数(词频)。
      • Positions: 该词项在对应文档中的位置(通常指在分词后的词语序列中的索引,从0开始)。
    • 示例:[1:1:[0]]表示文档1,出现1次,位置0

3、倒排索引的构建流程

1、文档解析与分析

(1)、分词(Tokenization)
  • 分词(Tokenization):
    • 使用分词器(Tokenizer)将文本拆分为词项。例如:
      • 英文:“The quick brown fox” → [“the”, “quick”, “brown”, “fox”]。
      • 中文:依赖分词器(如IK Analyzer、Jieba)将连续文本切分为有意义的词汇。
(2)、过滤
  • 过滤(Filtering):
    • 去除停用词(如"the", “is”)。
    • 词干提取(Stemming):将变形词还原为词根(如"running" → “run”)。
    • 同义词扩展:将"car"和"automobile"视为同一Term。

2、内存缓冲(In-Memory Buffer)

文档分词后,Term和文档ID临时存储在内存中。同时记录Term的频率和位置信息。Lucene使用FST(Finite State Transducer)在内存中临时存储。

  • Term → TermID映射
  • Document → Term列表

有限状态转换器FST(Finite State Transducer)是一种紧凑的有限状态机,支持高效前缀匹配和模糊查询。

  • Lucene使用FST存储Term字典,支持前缀压缩和快速查找。
  • 例如,Term “apple"和"app"共享前缀"app”,节省内存。

3、生成 Segment(段)

当内存缓冲区满或达到刷新间隔(如Elasticsearch的1秒),将数据写入磁盘生成Segment

Segment内部文件结构:
在这里插入图片描述
说明:

  • 所有文件都是只读的。
  • Segment是Lucene的最小可搜索单元。

4、合并 Segment(Merge)

  • 多个小Segment会影响查询性能(需查多个文件)。
  • Lucene定期执行Merge,将多个小Segment合并为大Segment
  • 合并过程中会:
    • 清除已删除文档
    • 压缩Posting List
    • 重建词典

注意:Merge是I/O密集型操作,但对性能至关重要。

5、分布式实现(如Elasticsearch)

  • 文档路由到分片:
    • 根据文档ID的哈希值%分片数确定目标分片。
    • 主分片(Primary Shard)负责写入,副本分片(Replica Shard)同步数据。
  • 事务日志(Translog):
    • 写入内存缓冲区时记录日志,防止数据丢失。
    • 定期(如每5分钟)持久化到磁盘并清空缓冲区(Flush操作)。

4、倒排索引的查询流程

以查询"lucene AND search"为例。

1、解析查询语句

java示例:

Query query = new BooleanQuery.Builder().add(new TermQuery(new Term("content", "lucene")), BooleanClause.Occur.MUST).add(new TermQuery(new Term("content", "search")), BooleanClause.Occur.MUST).build();

2、查找词项

  • 在.tim/.tip文件(单词表文件)中查找"lucene"和"search"。
  • 获取它们的TermID和倒排表指针。

注意:为了加快读取速度,lucene进行的优化。

  • 查询缓存(Query Cache):
    • 缓存频繁查询的Posting List结果(如过滤条件)。
  • 热词预热:
    • 提前加载高频Term的Posting List到内存。

3、读取倒排表

  • 并行读取两个倒排表。
    • lucene → [1,2,3]
    • search → [1,3]

4、求交集(Intersection)

  • 使用双指针算法或Skip List快速求交。
  [1,2,3][1,3]↑→ 公共DocID: [1,3]

5、计算相关性得分(Scoring)

使用BM25算法计算每个文档的相关性。

示例:
score(D, Q) = Σ BM25(t in Q)
其中:

  • t是查询词项
  • BM25考虑:词频(TF)、逆文档频率(IDF)、字段长度归一化

注意:Lucene默认使用BM25(比TF-IDF更先进)

6、排序并返回Top-K

  • 按得分排序
  • 返回前N个文档(支持分页)

5、倒排表的压缩技术(关键优化)

Posting List可能非常大,Lucene使用多种压缩技术。

1、Delta Encoding(差值编码)

  • DocID通常是递增的,存储差值而非原始值
  • 例如:[100, 102, 105] → [100, 2, 3]

2、Variable-Byte Encoding(VBCode)

  • 小整数用1字节存储,大整数用多字节
  • 节省空间,适合Posting List

3、PForDelta/Simple16

  • 高效压缩整数数组
  • 支持快速随机访问

4、Roaring Bitmap

  • 对于高频词(如"the"),使用位图代替列表
  • 极致压缩 + 快速集合运算(AND/OR)

6、Lucene中的高级结构:FST(Finite State Transducer)

1、什么是FST?

FST(有限状态转换器)是一种有向图数据结构,它扩展了FSA (Finite State Automaton, 有限状态自动机)。FST不仅可以判断一个字符串是否存在于集合中,还能将输入的字符串映射到一个输出值(例如,一个整数、另一个字符串或一个复杂对象)。
在Lucene的语境下,FST主要用于词典(Term Dictionary)的实现。

它的核心功能是:
输入:一个词项(Term),例如 “search”。
输出:该词项对应的元数据信息,例如在.tim文件中的偏移量指针、文档频率(DocFreq)等,这些信息用于快速定位和读取倒排列表。

2、FST是怎么实现的?(核心机制)

Lucene的FST实现(主要在org.apache.lucene.util.fst包中)基于以下几个关键设计。

(1)、状态(State)与弧(Arc):

状态(State):图中的节点。每个状态代表处理输入字符串过程中的一个中间点。
弧(Arc):连接状态的有向边。每条弧上标记着:

  • 输入标签(Input Label):通常是一个字符(或字节)。
  • 输出值(Output):一个可以累积的数值(在Lucene中通常是long类型)。这个输出值是FST强大功能的核心。
    从起始状态开始,沿着弧根据输入字符进行转移,最终到达一个终态。
(2)、路径输出的累积:

当从起始状态遍历到终态时,路径上所有弧的输出值会进行累积(通常是求和)。
这个累积的总输出值就是该输入字符串(词项)所映射的最终结果。
示例:查找"sea"。

  • s -> 弧输出100 -> 状态A
  • e -> 弧输出20 -> 状态B
  • a -> 弧输出5 -> 终态
  • 最终输出 = 100 + 20 + 5 = 125。这个125可能就是词项"sea"在索引文件中的偏移量。
(3)、高度压缩与共享:
  • 前缀共享(Prefix Sharing):这是FST空间效率高的根本原因。所有具有相同前缀的词项(如 “search”, “seek”, “sea”)会共享从起始状态到分叉点之前的路径。

  • 后缀共享 (Suffix Sharing):更进一步,FST构建算法(通常是基于排序的)会尝试让具有相同后缀的路径也进行共享。例如"cat", “bat”, "hat"的后缀"at"可以共享。

  • 最小化 (Minimization):构建过程会生成一个最小化的FST,即在保证功能相同的前提下,拥有最少的状态和弧。这极大地减少了存储空间。

(4)、与磁盘文件的结合:

FST本身存储的是词项到元数据指针的映射。
这个元数据指针(即FST查询的输出值)通常指向.tim文件中的某个位置,那里存储着更详细的词典信息(如词频、指向 .doc/.pos文件的指针等)。

.tip (Term Index)文件可以看作是一个“超级FST的索引”,它存储了.tim文件中大块FST数据块的入口指针,用于加速对.tim文件的随机访问。

3、FST优势

Lucene选择FST作为词典实现,是为了解决大规模索引中词典存储和查询的效率问题,其优势如下。
(1)、空间效率极高(High Space Efficiency)
核心优势。通过前缀和后缀的深度共享,FST能够以极小的空间存储海量的词项。这对于搜索引擎动辄数百万、上亿词项的场景至关重要。相比于简单的哈希表或Trie,FST的空间占用要小得多。
(2)、支持高效的前缀查询(Prefix Query)
由于前缀共享,查找所有以"searc"开头的词项非常高效。算法可以快速定位到代表"searc"的状态,然后遍历从该状态出发的所有路径,即可得到所有匹配的词项及其输出值。
(3)、支持模糊查询(Fuzzy Query)
FST可以相对高效地支持基于编辑距离(Levenshtein Distance)的模糊查询。算法可以在状态图上进行“容错”遍历,允许一定次数的插入、删除、替换操作,找到与查询词项编辑距离在阈值内的所有词项。
(4)、查询性能优秀(Good Query Performance)
查找一个词项的时间复杂度接近O(词项长度)。虽然比哈希表的O(1)稍慢,但考虑到其巨大的空间优势,这个查询速度对于搜索引擎来说是完全可以接受的,并且远快于线性扫描。
(5)、支持复杂映射(Supports Complex Mapping):
FST的“转换”特性允许它将字符串映射到任意(可累积的)数值。Lucene利用这一点,将词项映射到其在磁盘文件中的物理位置(偏移量),实现了词典逻辑与物理存储的解耦。
(6)、支持正则匹配 (Regular Expression Matching):
FST本质上是正则语言的接受器,因此可以支持正则表达式查询。
在这里插入图片描述

总结来说,**Lucene采用FST是一个在空间和时间复杂度之间取得卓越平衡的设计。它牺牲了哈希表极致的查询速度(O(1)),换来了数量级上的空间压缩,并同时获得了对前缀、模糊、正则等复杂查询的天然支持。**这种设计使得Lucene能够在有限的硬件资源下,构建和维护极其庞大的倒排索引,是其高性能的关键基石之一。

7、实现示例

1、Lucene的倒排索引实现

  • Segment结构:
    • 每个Segment包含:
      • Term Dictionary:FST存储。
      • Posting List:按文档ID排序并压缩。
      • 字段存储(Stored Fields):原始文档字段值。
  • 实时搜索优化:
    • 默认1秒刷新(Refresh)生成新Segment,实现近实时(NRT)搜索。

2、Elasticsearch的分布式索引

写入流程:
1、客户端发送请求到协调节点(Coordinating Node)。
2、协调节点路由文档到主分片(Primary Shard)。
3、主分片写入内存缓冲区和Translog,同步到副本分片(Replica Shard)。
4、定期刷新生成Segment,合并优化。

8、倒排索引vs正排索引vs列存(Doc Values)

在这里插入图片描述
注意:

  • 倒排索引适合“找包含某词的文档”
  • Doc Values适合“按price排序”或“按category聚合”

9、Lucene中如何查看倒排索引?

可以使用Lucene自带的IndexReader查看索引内容。

java示例:

Directory directory = FSDirectory.open(Paths.get("/path/to/index"));
IndexReader reader = DirectoryReader.open(directory);// 获取某个字段的Terms
Terms terms = reader.terms("content");
TermsEnum termsEnum = terms.iterator();while (termsEnum.next() != null) {BytesRef term = termsEnum.term();System.out.println("Term: " + term.utf8ToString());// 获取倒排表PostingsEnum postings = termsEnum.postings(null);while (postings.nextDoc() != PostingsEnum.NO_MORE_DOCS) {int docId = postings.docID();int freq = postings.freq(); // 词频System.out.println("DocID: " + docId + ", Freq: " + freq);}
}

10、总结:倒排索引的核心要点

倒排索引通过词项到文档的映射解决了大规模文本数据的快速检索问题。其构建流程包括分词、排序、Segment生成和合并优化,结合压缩、缓存、分布式等技术进一步提升性能。无论是Lucene的单机实现,还是Elasticsearch的分布式架构,倒排索引都是高效搜索的核心基础。实际应用中需结合分词、压缩和分布式策略,平衡存储成本与查询效率。

重点:
在这里插入图片描述

为什么倒排索引如此高效?

  • 空间换时间:预构建索引,牺牲存储换取查询速度。
  • 只读设计:Segment不可变,便于缓存和并发读。
  • 分治思想:大索引拆分为多个Segment,支持并行处理。
  • 压缩优化:减少磁盘I/O,提升缓存命中率。

向阳前行,Dare To Be!!!

http://www.dtcms.com/a/601132.html

相关文章:

  • 一个基于Python Streamlit sqlite3 的销售单管理系统,提供商品管理、客户管理、销售单管理及打印,和应收对账单等功能
  • 网站权重是什么华大基因背景调查
  • 华为桌面图标模糊问题解决方案
  • MotionTrans: 从人类VR数据学习机器人操作的运动级迁移
  • [Dify 实战案例] 构建一个 CV 内容优化与润色助手:让 AI 成为你的简历教练
  • 【计算思维】蓝桥杯STEMA 科技素养考试真题及解析 B
  • Kanass实战教程系列(3) - 项目经理如何使用kanass有效管理项目
  • 成都网站建设 网络公司建设工程中标查询网站
  • C语言编译程序属于应用软件 | 理解编译原理与应用场景
  • 蛋糕网站模板汕头网络营销公司
  • HOT100题打卡第37天——贪心算法
  • Python学习历程——模块
  • bin文件反编译C语言 | 深入探讨反编译技术及其应用
  • 测开学习DAY27
  • dede cms 网站模板云匠网怎么样
  • 信息学奥赛一本通 1625:【例 1】反素数 Antiprime | 洛谷 P1463 [POI 2001 R1 / HAOI2007] 反素数
  • 如何做网站长尾关键词布局工程公司取名字大全
  • 深度学习:从零开始手搓一个深层神经网络
  • Docker 多服务镜像构建完整教程
  • Docker 启动 EMQX 5.x 并配置自签名证书
  • 网站招工费怎么做会计分录小小视频免费观看高清
  • C++重点知识梳理(上)
  • 长沙市建设局官方网站wordpress 显示文章标签
  • 基于用户评论分析挖掘的旅游景点推荐系统
  • 宣传旅游网站建设的重点是什么装修公司哪家好排名
  • 【C语言学习笔记】动态内存分配:malloc/free的正确打开方式
  • HOVER:用于人形机器人的多功能全身神经控制器
  • 学会给网页穿衣服——学习 CSS 语言
  • Android11-Launcher3 定制-去除副屏幕-可以滑动效果 - 篇二
  • 在 ubuntu怎么创建一个新用户,并且设置为默认自动登录用户,最简单