ElasticSearch为什么快

ElasticSearch作为分布式搜索引擎,其快的原因在于近实时搜索和文档写入速度。通过索引、分片、副本和事务日志实现高效的数据处理。倒排索引和分析过程确保全文检索的性能。此外,通过设置索引别名和调整刷新频率等优化策略,进一步提升系统性能。

ElasticSearch是分布式搜索引擎,可以存储、检索、搜索、统计数据。
那使用ES主要原因是:1、全文检索 2、快

这篇文章主要讲解为什么快?
首先明确ElasticSearch快在搜索是近实时的,文档的CRUD也是实时的。

一、ES的基本架构图及基础概念:

在这里插入图片描述

  • Index 索引
    ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合。
    它是个逻辑命名空间,类似于数据库概念中的数据库

  • Type 类型
    类型是索引内部的逻辑分区(category/partition),然而其意义完全取决于用户需求。因此,一个索引内部可定义一个或多个类型(type)。
    类似于数据库概念中的一张

  • **Document 文档 **
    文档是索引和搜索的原子单位,它是包含了一个或多个域(Field)的容器,基于JSON格式进行表示。文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,有多个值的域通常称为“多值域”。
    每个文档可以存储不同的域集,但同一类型下的文档至应该有某种程度上的相似之处。
    它是具体事实数据的体现,类似于数据库概念中的 一条记录

  • SHARD 分片
    • index包含多个shard,每个shard都是一个最小工作单元,承载部分数据
    • 每个shard都是一个lucene实例,有完整的建立索引和处理请求的能力。
    • 增减节点时,shard会自动在nodes之间负载均衡
    • shard分为primary shard和replica shard,每个document只存在于某一个primary shard以及其对应的replica shard中
    • replica shard是primary shard的副本,负责容错,以及承担读请求负载提高搜索性能。
    • primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改,primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard

  • translog
    事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。作为临时文件存储在硬盘上。理想中应该是任何一次写入都刷入磁盘,但是性能考虑不可能。实际上是每几秒中调用一次fsync刷到磁盘来提高吞吐量。这当然带来了丢失数据的可能。

二、文档写入速度快

1、当Client有一个writing request时,ES cluster接受到request之后是如何运作的
首先明确新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的 复制分片上。复制分片只能负载读请求。

其中ES如何决定文档去存在哪个主分片上呢?
进程不能是随机的,因为我们将来要检索文档。其实是由一个简单的算法决定:

shard = hash(routing) % number_of_primary_shards

routing 值是一个任意字符串,它默认是 _id 但也可以自定义
在这里插入图片描述
下面我们罗列在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤:

  1. 客户端给 Node 1 发送新建、索引或删除请求。
  2. 节点使用文档的 _id 确定文档属于分片 0 。它转发请求到 Node 3 ,分片 0 位于这个节 点上。
  3. Node 3 在主分片上执行请求,如果成功,它转发请求到相应的位于 Node 1 和 Node 2 的 复制节点上。当所有的复制节点报告成功, Node 3 报告成功到请求的节点,请求的节点 再报告给客户端。 客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片。你的修改生效了。

由这个流程可以看出来,ES集群接收到写请求后会根据_id将文档发送到相应的主分片所在的node上写入,不同的主分片会分布到不同的node上,因此不存在写瓶颈。

2、ES具体的写入过程
• 第一步,新文档被写入内存,操作被写入buffer与translog
在这里插入图片描述
• 第二步,refresh操作
ES中默认每1秒中进行一次refresh。
refresh操作会清空buffer中的数据(translog数据不变),在这1秒时间内写入内存的新文档构成一个分段(segment)被写入一个文件系统缓存(filesystem cache)中,segment可以被打开检索到,但是尚未写入硬盘。
ES检索数据会同时检索buffer和segment,这样保证数据每一次都能拿到最新的数据
在这里插入图片描述
• 第三步,不断累积数据,重复数据写到buffer和translog,每隔一秒将生成一个新的segment,而translog会记录增长的文档,变得越来越大。
在这里插入图片描述
• 第四步,一定时间后,执行flush操作。首先会将buffer中的数据写到segment中,再将所有的segment写入到磁盘上。最后清空buffer,translog,所有segment处于commit状态。
在这里插入图片描述
• 整个过程中,ES通过后台合并段解决 生成大的segmentt太多影响搜索性能的问题。
如图:两个提交的段和一个未提交的段合并为了一个更大的段,这个过程不会中断索引和搜索
在这里插入图片描述
当新的段flush到了硬盘(只会写入新的段,旧的段会被删除),新的段打开供搜索
在这里插入图片描述
*segment合并极为消耗资源,所以一般情况下ES会对段合并消耗的资源加以限制。

由此流程可以看出,在具体写入过程中,文档只要同时写到buffer和translog中就算写入成功了,buffer在内存中,而translog只要追加一条,所以这个速度是非常快的。后续的操作由ES自己完成。另外修改和删除对于ES来说也会增加1条新纪录,只是会修改版本。

二、为什么搜索快?(近实时搜索的原理)

用户写入可以立即被搜索到–ES建立索引保证查询快–用户查询时,多node负载

1、首先能够近实时的搜索是因为以上的写入机制,保证了新插入的文档能够实时被索引
2、分布式搜索的执行方式(多node负载)
搜索的过程分为两个阶段:查询、取回

查询阶段:
在初始化查询阶段(query phase) ,查询被向索引中的每个分片副本(原本或副本) 广播。
每个分片在本地执行搜索并且建立了匹配document的优先队列(priority queue)
在这里插入图片描述

查询阶段包含以下三步:
1.客户端发送一个 search(搜索) 请求给 Node 3 , Node 3 创建了一个长度为 from+size 的空优
先级队列。
 2. Node 3 转发这个搜索请求到索引中每个分片的原本或副本。每个分片在本地
执行这个查询并且结果将存到一个大小为 from+size 的有序本地优先队列里去。 
3.每个分片返回document的ID和它优先队列里的所有document的排序值给协调节点 Node 3 。 Node
3 把这些值合并到自己的优先队列里产生全局排序结果。

取回阶段:
查询阶段辨别出那些满足搜索请求的document,但我们仍然需要取回那些document本身。这
就是取回阶段的工作。
在这里插入图片描述

分发阶段由以下步骤构成:
1.协调节点辨别出哪个document需要取回,并且向相关分片发出 GET 请求。
2.每个分片加载document并且根据需要丰富(如高亮等)它们,然后再将document返回协调节点。
3.一旦所有的document都被取回,协调节点会将结果返回给客户端。

对于这些GET请求,协调节点会轮询所有的分片副本以分摊负载。
3、为了方便在全文文本字段中进行查询,Elasticsearch首先对文本分析(analyzes),然后使用结果建立一个倒排索引。

3.1 倒排索引(inverted index):
由在文档中出现的唯一的单词列表,以及对于每个单词在文档中的位置组成
如:我们有两个文档,每个文档 content 字段包含:

  1. The quick brown fox jumped over the lazy dog
  2. Quick brown foxes leap over lazy dogs in summer

为了创建倒排索引,我们首先切分每个文档的 content 字段为单独的单词(我们把它们叫做词(terms)或者表征(tokens))把所有的唯一词放入列表并排序,结果是这个样子的:
在这里插入图片描述
现在,如果我们想搜索 “quick brown” ,我们只需要找到每个词在哪个文档中出现即可:
在这里插入图片描述
两个文档都匹配,但是第一个比第二个有更多的匹配项。 如果我们加入简单的相似度算法(similarity algorithm),计算匹配单词的数目,这样我们就可以说第一个文档比第二个匹配度更高

但是在我们的倒排索引中还有些问题:

  1. “Quick” 和 “quick” 被认为是不同的单词,但是用户可能认为它们是相同的。
  2. “fox” 和 “foxes” 很相似,就像 “dog” 和 “dogs” ——它们都是同根词。
  3. “jumped” 和 “leap” 不是同根词,但意思相似——它们是同义词。

而户可以合理地希望两个文档都能匹配查询,所以需要对terms进行标椎话的过程。而这个过程需要分词器进行分析.

3.2、分析(analysis):
要实现倒排索引,先要对文本分析–使用分词器(用于将文本切割成单独的单词terms/tokens)

分析(analysis)是这样一个过程:
(1)首先,标记化一个文本块为适用于倒排索引单独的词(term)
(2)然后标准化这些词为标准形式,提高它们的“可搜索性”或“查全率”
这个工作是分析器(analyzer)完成的。一个分析器(analyzer)只是一个包装用于将三个功能放
到一个包里:
(1)字符过滤器
首先字符串经过字符过滤器(character filter),它们的工作是在标记化前处理字符串。字符过
滤器能够去除HTML标记,或者转换 "&" 为 "and" 。
(2)分词器
下一步,分词器(tokenizer)被标记化成独立的词。一个简单的分词器(tokenizer)可以根据空
格或逗号将单词分开(译者注:这个在中文中不适用) 。
(3)标记过滤
最后,每个词都通过所有标记过滤(token filters),它可以修改词(例如将 "Quick" 转为小
写) ,去掉词(例如停用词像 "a" 、 "and" 、 "the" 等等) ,或者增加词(例如同义词
像 "jump" 和 "leap" )

三、详解倒排索引的架构

在这里插入图片描述
如上是Lucene中实际的索引结构。用例子来说明上述三个概念:
在这里插入图片描述
ID是文档id,那么建立的索引如下:

Name:
在这里插入图片描述
Age:
在这里插入图片描述
Sex:
在这里插入图片描述
Posting List
可见为每个 field 都建立了一个倒排索引。Posting list就是一个int的数组,存储了所有符合某个term的文档id。实际上,除此之外还包含:文档的数量、词条在每个文档中出现的次数、出现的位置、每个文档的长度、所有文档的平均长度等,在计算相关度时使用。

Term Dictionary
假设我们有很多个 term,比如:

Carla,Sara,Elin,Ada,Patty,Kate,Selena

如果按照这样的顺序排列,找出某个特定的 term 一定很慢,因为 term 没有排序,需要全部过滤一遍才能找出特定的 term。排序之后就变成了:

Ada,Carla,Elin,Kate,Patty,Sara,Selena

这样我们可以用二分查找的方式,比全遍历更快地找出目标的 term。这个就是 term dictionary。有了 term dictionary 之后,可以用 logN 次磁盘查找得到目标。

Term Index
但是磁盘的随机读操作仍然是非常昂贵的(一次 random access 大概需要 10ms 的时间)。所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个 term dictionary 本身又太大了,无法完整地放到内存里。于是就有了 term index。term index 有点像一本字典的大的章节表。比如:

A 开头的 term ……………. Xxx 页

C 开头的 term ……………. Yyy 页

E 开头的 term ……………. Zzz 页

如果所有的 term 都是英文字符的话,可能这个 term index 就真的是 26 个英文字符表构成的了。但是实际的情况是,term 未必都是英文字符,term 可以是任意的 byte 数组。而且 26 个英文字符也未必是每一个字符都有均等的 term,比如 x 字符开头的 term 可能一个都没有,而 s 开头的 term 又特别多。实际的 term index 是一棵 trie 树(字典树):
在这里插入图片描述

例子是一个包含 “A”, “to”, “tea”, “ted”, “ten”, “i”, “in”, 和 “inn” 的 trie 树。这棵树不会包含所有的 term,它包含的是 term 的一些前缀。通过 term index 可以快速地定位到 term dictionary 的某个 offset,然后从这个位置再往后顺序查找。

现在我们可以回答“为什么 Elasticsearch/Lucene 检索可以比 mysql 快了。Mysql 只有 term dictionary 这一层,是以 b-tree 排序的方式存储在磁盘上的。检索一个 term 需要若干次的 random access 的磁盘操作。而 Lucene 在 term dictionary 的基础上添加了 term index 来加速检索,term index 以树的形式缓存在内存中。从 term index 查到对应的 term dictionary 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数。

注:实际上,Lucene 内部的 Term Index 是用的「变种的」trie树,即 FST 。FST 比 trie树好在哪?trie树只共享了前缀,而 FST 既共享前缀也共享后缀,更加的节省空间。

四、ES的一些设置

1、设置索引别名实现零停机,重新索引过程中的问题是必须更新你的应用,来使用另一个索引名
例如index ->index_v1、index-_v2
2、如果更想要优化索引,而不是实时搜索(检测到最新更改的内容),可以修改refresh_interval减少刷新频率。在创建大量索引的时候可以关闭自动刷新,到使用索引时再打开。
3、通过设置perference 防止结果震荡
结果震荡:

想像一下,你正在按照 timestamp 字段来对你的结果排序,并且有两个document有相同的timestamp。
由于搜索请求是在所有有效的分片副本间轮询的,这两个document可能在原始分片里是一种顺序,在副本分片里是另一种顺序。
这就是被称为结果震荡(bouncing results) 的问题:用户每次刷新页面,结果顺序会发生变化。
避免这个问题方法是对于同一个用户总是使用同一个分片。
方法就是使用一个随机字符串例如用户的会话ID(session ID)来设置 preference 参数。

preference
preference 参数允许你控制使用哪个分片或节点来处理搜索请求。她接受如下一些参数
_primary , _primary_first , _local , _only_node:xyz ,_prefer_node:xyz 和 _shards:2,3 。
这些参数在文档搜索偏好里有详细描述。
然而通常最有用的值是一些随机字符串,它们可以避免结果震荡问题(the bouncing results problem) 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值