ES细节说明(倒排索引、文档分析、文档冲突...)

传统的数据库每个字段存储单个值,但这对全文检索并不够。文本字段中的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值的能力。最好的支持是一个字段多个值需求的数据结构是倒排索引。

一、倒排索引概念

Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。见其名,知其意,有倒排索引,肯定会对应有正向索引。

正向索引:(可以类比mysql数据库),就是搜索引擎会将待搜索的文件都对应一个文件 ID,搜索时将这个ID 和搜索关键字进行对应,形成 K-V 对,然后对关键字进行统计计数.
倒排索引:即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。

一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文
档列表。例如,假设我们有两个文档,每个文档的 content 域包含如下内容:

  • The quick brown fox jumped over the lazy dog
  • Quick brown foxes leap over lazy dogs in summer

为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的词(我们称它为 词条或 tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:
请添加图片描述
现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:
请添加图片描述
两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单相似性算法,那么我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

二、文档搜索

早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。倒排索引被写入磁盘后是 不可改变 的:它永远不会修改。
如何在保留不变性的前提下实现倒排索引的更新?

用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。
Elasticsearch 基于 Lucene, 这个 java 库引入了按段搜索的概念。 每一段本身都是一倒排索引, 但索引在 Lucene 中除表示所有段的集合外, 还增加了提交点的概念 — 一个列出了所有已知段的文件

按段搜索的执行流程如下

  1. 新文档被收集到内存索引缓存
  2. 不时地, 缓存被 提交
    (1) 一个新的段—一个追加的倒排索引—被写入磁盘。
    (2) 一个新的包含新段名字的 提交点 被写入磁盘
    (3) 磁盘进行 同步 — 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件
  3. 新的段被开启,让它包含的文档可见以被搜索
  4. 内存缓存被清空,等待接收新的文档

请添加图片描述

三、近实时搜索&持久化变更&段合并

1.近时搜索

写操作的延时如下图所示

请添加图片描述
在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。这些行为可能会对新用户造成困惑: 他们索引了一个文档然后尝试搜索它,但却没有搜到。这个问题的解决办法是用 refresh API 执行一次手动刷新: /users/_refresh

2.持久变更

translog 的目的是保证操作不会丢失,在文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失。默认 translog 是每 5 秒被 fsync 刷新到硬盘, 或者在每次写请求完成之后执行(e.g. index, delete, update, bulk)。这个过程在主分片和复制分片都会发生。
请添加图片描述

1 .一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog
2 .刷新(refresh)使分片每秒被刷新(refresh)一次:

  • 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。
  • 这个段被打开,使其可被搜索
  • 内存缓冲区被清空

3 .这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志
4 .每隔一段时间—例如 translog 变得越来越大—索引被刷新(flush);一个新的 translog被创建,并且一个全量提交被执行

  • 所有在内存缓冲区的文档都被写入一个新的段。
  • 缓冲区被清空。
  • 一个提交点被写入硬盘。
  • 文件系统缓存通过 fsync 被刷新(flush)。
  • 老的 translog 被删除。

3.段合并

由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
启动段合并不需要你做任何事。进行索引和搜索时会自动进行。下面是合并流程:

  1. 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。
  2. 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。
  3. 一旦合并结束,老的段被删除
  • 新的段被刷新(flush)到了磁盘。 ** 写入一个包含新段且排除旧的和较小的段的新提交点。
  • 新的段被打开用来搜索。
  • 老的段被删除。

请添加图片描述

合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。Elasticsearch
在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。

四、文档分析

分析包含下面的过程:

  • 将一块文本分成适合于倒排索引的独立的 词条
  • 将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall

分析器执行上面的工作时,分析器实际上是将下面的三个功能封装到了一个包里:

字符过滤器:首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉 HTML,或者将 & 转化成 and。
分词器:其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
Token 过滤器:最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

1.测试内置的分析器

下面是使用标准分析器,可以看出Text to analyze已经被分词处理了
在这里插入图片描述

2.使用IK分词器

首先我们为什么要用IK分词器?因为我们发现默认的分词器对中文的处理不太好,例如下面的,为了解决这种问题,我们可以考虑使用IK分词器。
在这里插入图片描述

请点击下载-----》IK分词器的下载地址
下载后将其解压到elasticsearch的plugins目录下,然后重启ES
请添加图片描述
注意!要删去plugins中的隐藏文件.DS_Store,不然启动会报错:
请添加图片描述
注意安装路径不要出现中文名,如果还有报错试试下面的操作!
在这里插入图片描述
使用发现,测试用例很成功:
在这里插入图片描述

但我们会有下面的需求,IK分词器对某些词汇依旧无法识别例如弗雷尔卓德这个词!
首先进入 ES 根目录中的 plugins 文件夹下的 ik 文件夹,进入 config 目录,创建 custom.dic文件,写入。同时打开 IKAnalyzer.cfg.xml 文件,将新建的 custom.dic 配置其中,重启 ES 服务器。然后重启ES
请添加图片描述
发现已经能识别该词了!very good~请添加图片描述

3.自定义分析器

虽然 Elasticsearch 带有一些现成的分析器,然而在分析器上 Elasticsearch 真正的强大之处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单元过滤器来创建自定义的分析器。

# PUT http://localhost:9200/my_index
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "]
}},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": [ "the", "a" ]
}},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": [ "html_strip", "&_to_and" ],
"tokenizer": "standard",
"filter": [ "lowercase", "my_stopwords" ]
}}
}}}

然后在用刚刚创建的索引的分词器来分析下面的语句,发现很成功!
请添加图片描述

五、文档处理(文档冲突)

在并发的对ES文档进行更改时,非常容易将更改丢失。变更越频繁,读数据和更新数据的间隙越长,也就越可能丢失变更。
在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失:

悲观并发控制:这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改。

乐观并发控制:Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作(CAS嘛) 我们可以利用 version 号来确保应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。

老的版本 es 使用 version,但是新版本不支持了,会报下面的错误,提示我们用 if_seq_no和 if_primary_term

1.比如下面我存放一条数据,默认就会有version以及seq_no和primary_term

请添加图片描述

2.我们在不考虑并发冲突的情况下修改数据,发现这些值确实有变化!

请添加图片描述

3.所以我们可以借用上面的字段,利用CAS思想来解决并发的冲突问题,先举个错误的案例,比如下面的if_seq_no 和 if_primary_term和文档现在的数据不一致,则会操作失败。

请添加图片描述

4.想要修改成功就必须把if_seq_no 和 if_primary_term写对,如下图的案例:

请添加图片描述

5.也可以使用外部系统版本控制

注意:version一定要比原版本高,经过本人测试,这里貌似不能使用局部修改
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

键盘歌唱家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值