Elasticsearch以及Lucene详解

Lucene

在了解Elasticsearch之前需要先了解Lucene,Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包。是一个高性能、可扩展的全文检索引擎库,其核心原理基于倒排索引分词技术,广泛应用于搜索引擎、文档检索等场景。Elasticsearch就是基于Lucene实现的。

1. 核心数据结构:倒排索引(Inverted Index)
  1. 正向索引(Forward Index):文档 → 单词列表,如书籍目录按页码顺序列出内容。
  2. 倒排索引(Inverted Index):单词 → 文档列表,类似书籍末尾的“关键词索引”。

假设有3篇文档:

  • Doc1: “I love apple”
  • Doc2: “apple is delicious”
  • Doc3: “I hate apple”

倒排索引如下:

Term    | Posting List
-----------------------------
"I"     | [Doc1(位置1), Doc3(位置1)]
"apple" | [Doc1(位置3), Doc2(位置1), Doc3(位置3)]
"love"  | [Doc1(位置2)]
2. 核心流程:倒排索引创建
  1. 使用分词器(Analyzer)将文本拆分为单词(Term)。例如:“数据库原理” → [“数据库”, “原理”]。这个过程中有一些处理:中文需分词,英文需去除停用词如“the”;大小写转换(“Apple” → “apple”);词干提取(“running” → “run”);同义词扩展等等。
  2. 词汇表的建立:Lucene使用一个称为词汇表(Vocabulary)的数据结构来存储所有出现在文本中的唯一单词。在建立词汇表时,Lucene会去重并对单词进行排序,以提高检索效率。
  3. 构建倒排索引:记录每个Term出现的文档ID、位置、频率等信息。
3. 查询过程

搜索:当用户输入一个检索词时,Lucene会在词汇表中查找该单词,并获取到包含该单词的段落列表。然后,Lucene根据这些段落列表来计算每个段落的相关度得分,并按照相关度排序返回结果。

相关性的算法有:TF-IDF、BM25等

  • TF-IDF算法:TF-IDF是一种用于评估一个词语对于一个文件集或一个语料库中的某个文件的重要程度的统计方法。基本思想是:如果一个词在一个文件中出现的频率越高,同时在整个文件集中出现的频率越低,那么该词对于该文件的区分能力越强,也就是越重要。
  • BM25算法:核心思想是使用统计学原理来计算文档和查询之间的相关性。它考虑了文档中各个词项的权重、查询词频、文档长度等因素,通过计算一个分数来衡量文档和查询之间的相关性。

原文链接:https://blog.csdn.net/weixin_42506246/article/details/136570366

Elasticsearch

ElaticSearch (通常简写为 ES )是一个开源的、高扩展的分布式全文检索引擎。可以做到近乎实时地存储、检索数据,并且本身具有良好的扩展性,可以扩展到上百台服务器,处理PB级别的数据。

ES 常用于日志分析、搜索引擎、数据分析等场景。

数据存储

Elasticsearch 所有的数据以 索引(Index) 的形式存储,一个个 索引(Index) 类似于数据库的表。

  • 文档(Document):文档(Document)是 JSON 格式的基本数据单元,可以认为就是一段JSON格式的文本信息,后续文档(Document)会被分词并构建倒排索引。
  • 索引(Index):索引(Index)是所有文档的倒排索引,每个索引(Index)有唯一的名称(如 products)。
  • 字段(Field):字段是文档(Document)中的最小数据单元,对应 JSON 中的一个键值对。每个字段有 名称 和 值,并关联特定的 数据类型(如文本、数值、日期等)。
  • Mapping(映射):用于定义:文档包含哪些字段。每个字段的数据类型和属性(如分词规则、是否索引)。类似于关系型数据库中的 表结构设计

我们以商品搜索举例,我们有很多个商品,每个商品的信息以JSON的形式存在,下面就是一个文档(Document)的示例:

{
  "id": 1,
  "name": "iPhone 15",
  "description": "高性能智能手机,搭载A16芯片",
  "price": 7999
}

字段(Field)就是id、name、description、price这几个key。

Mapping(映射)就是id是int型,name是文本型,类似于表结构。

索引(Index)就是所有商品信息转为的倒排索引,例如:

词项(Term)	   出现的文档(Posting List)
"iphone"	   [Doc1(name字段), Doc1(description字段)]
"手机"	       [Doc1(description字段)]
"macbook"	   [Doc2(name字段)]
"m2"	       [Doc2(description字段)]
"芯片"	       [Doc1(description字段)]

集群分片

一个 索引 可以存储超出单个结点硬件限制的大量数据。比如,一个具有 10亿文档的索引占据 1TB 的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。

为了解决这个问题,Elasticsearch 提供了将索引划分成多份的能力,这些份就叫做 分片。当你创建一个索引的时候,你可以指定你想要的 分片的数量。每个分片本身也是一个功能完善并且独立的 索引,这个 索引 可以被放置到集群中的任何节点上。

分片之所以重要,主要有两方面的原因:

  1. 允许你水平分割/扩展你的内容容量
  2. 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量
  3. 一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由 Elasticsearch 管理的,对于作为用户的你来说,这些都是透明的

集群副本

副本是一个分片的精确复制,每个分片可以有零个或多个副本。副本的作用:

  1. 提高系统的容错性,当某个节点某个分片损坏或丢失时,可以从副本中恢复。
  2. 提高 ES 查询效率,ES 会自动对搜索请求进行负载均衡。

那么如何保证数据一致性呢?

为了提高写入系统的 resiliency(弹性),索引操作可以配置为在继续索引之前等待一定数量的活动副本分片,如果所需的活动副本分片数没有达到指定数量,那么写入操作必须等待并且重试,直到必需的副本分片数已启动,或者发生了超时为止。

ElasticSearch 写原理

写一个新文档的流程如下:

  1. 用户向一个节点提交了一个索引新文档的请求,节点会计算新文档应该加入到哪个分片(shard)中。每个节点都有每个分片存储在哪个节点的信息,因此协调节点会将请求发送给对应的节点,注意这个请求会发送给主分片。
  2. 主分片完成索引。
  3. 主分片会并行将请求发送到其所有副本分片,保证每个分片都持有最新数据。

主分片写入文档的流程如下:

  1. 每次写入新文档时,都会先写入 内存 中,并将这一操作写入一个 translog 文件(transaction log)中,此时如果执行搜索操作,这个新文档还不能被索引到。
  2. ES 会每隔 1 秒时间(这个时间可以修改)进行一次刷新操作(refresh),此时在这 1 秒时间内写入内存的新文档都会被写入一个文件系统缓存(filesystem cache)中,并构成一个分段(segment)。此时这个 segment 里的文档可以被搜索到,但是尚未写入硬盘,即如果此时发生断电,则这些文档可能会丢失。这就是我们为什么说es是近实时的搜索引擎而不是实时的,也就是说给索引插入一条数据后,我们需要等待1秒才能被搜到这条数据。
  3. 不断有新的文档写入,则这一过程将不断重复执行。每隔一秒将生成一个新的 segment,而 translog 文件将越来越大。
  4. 每隔 30 分钟或者 translog 文件变得很大,则执行一次 fsync 操作。此时所有在文件系统缓存中的 segment 将被写入磁盘,而 translog 将被删除(此后会生成新的 translog)
  5. 由于每一秒就会生成一个新的 segment,很快将会有大量的 segment。对于一个分片进行查询请求,将会轮流查询分片中的所有 segment,这将降低搜索效率。因此 ES 会自动启动合并 segment 的工作,将一部分相似大小的 segment合并成一个新的大 segment。合并的过程实际上是创建了一个新的 segment,当新 segment 被写入磁盘,所有被合并的旧 segment 被清除。

由上面的流程可以看出,在两次 fsync 操作之间,存储在内存和 文件系统 缓存中的文档是不安全的,一旦出现断电这些文档就会丢失。所以 ES 引入了 translog 来记录两次 fsync 之间所有的操作,这样机器从故障中恢复或者重新启动,ES 便可以根据 translog 进行还原

当然,translog 本身也是文件,存在于内存当中,如果发生断电一样会丢失。因此,ES 会在每隔 5 秒时间或是一次写入请求完成后将 translog 写入磁盘。可以认为一个对文档的操作一旦写入磁盘便是安全的可以复原的,因此只有在当前操作记录被写入磁盘,ES 才会将操作成功的结果返回发送此操作请求的客户端。

ElasticSearch 倒排索引的优化

Term Dictionary优化

Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库通过B-Tree的方式类似啊,为什么说比B-Tree的查询快呢?

Term Index优化

但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树:这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。

在这里插入图片描述

联合索引

如果多个field索引的联合查询,倒排索引如何满足快速查询的要求呢?

  1. 利用跳表(Skip list)的数据结构快速做“与”运算,或者
  2. 利用bitmaps按位“与”,bitmaps见下方的Roaring bitmaps介绍
压缩技术
  1. Roaring bitmaps:将所有的文档id按照65535为界限分块,比如第一块所包含的文档id范围在0到65535之间,第二块的id范围是65536到131071,以此类推,会有多个65535大小的块。再用<商,余数>的组合表示每一组id,每一组id都存0到65535的数字,并且存下它对于65535的倍数,就可以存下它的id了。这样每组的id范围都在0到65535内,但是加上65535的倍数就能获取到真实的id了。这样还有一个好处,就是当我们要查某个id是否存在时,就可以快速查到。
  2. FST压缩技术:以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存。
  3. 增量编码压缩,将大数变小数,按字节存储

ElasticSearch 为什么那么快

  1. 索引分片,查询可并发操作。通过分布式部署,将数据存储在多个节点。
  2. 倒排索引(核心):将文档中的每个词与该词出现在哪些文档中进行映射并储存。检索时通过倒排索引快速找到包含所有搜索次的文档,不需要重新计算。并且有很多索引优化错事:支持索引覆盖、索引下推等优化技术。快速通过跳表Bitmap索引等组织检索,通过联合索引定位范围。
  3. 异步请求处理。请求到达时立刻返回部分结果。
  4. 内存存储:读写数据时减少磁盘访问次数。
  5. 压缩:通过压缩技术尽可能的在内存中多放数据。

https://zhuanlan.zhihu.com/p/668265103
https://blog.csdn.net/SunshineTan/article/details/101425651

https://blog.csdn.net/m0_74436895/article/details/142526240
https://blog.csdn.net/Reggie97/article/details/137478781

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值