Elasticsearch 入门(5):路由计算,分片控制,读写流程,更新流程,分段索引,近实时搜索,持久化变更

路由计算

当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?

  • 首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道 从何处寻找了。实际上,这个过程是根据下面这个公式决定的:

    s h a r d = h a s h ( r o u t i n g ) % n u m b e r _ o f _ p r o m a r y _ s h a r d s shard=hash(routing) \quad \% \quad number\_of\_promary\_shards shard=hash(routing)%number_of_promary_shards

  • routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。

  • routing 通过 hash 函数生成一个数字,然后这个数字再除以number_of_primary_shards(主分片的数量)

  • 就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

  • 所有的文档 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一个叫做 routing 的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。

  • 一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。

分片控制

  • 我们假设有一个集群由三个节点组成。 它包含一个叫 emps 的索引,有两个主分片,每个主分片有两个副本分片。相同分片的副本不会放在同一节点。
    在这里插入图片描述
  • 通过 elasticsearch-head 插件查看集群情况,所以我们的集群是一个有三个节点和一个索引的集群。
    在这里插入图片描述
  • 我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。
  • 在下面的例子中,将 所有的请求发送到 Node 1,我们将其称为 协调节点(coordinating node)
  • 注意:当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点。

写流程

步骤流程

  • 新建、索引和删除请求都是 写 操作, 必须在主分片上面完成之后才能被复制到相关的副本分片
    在这里插入图片描述

新建,索引和删除文档所需要的步骤顺序: 感觉和mysql的负载均衡差不多

  1. 客户端向 Node 1 发送新建、索引或者删除请求。
  2. 节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。
  3. Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1Node 2 的副本分片上。
  4. 一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。

在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为 Elasticsearch 已经很快,但是为了完整起见,请参考下面表格:

参数:consistency

consistency,即一致性。在默认设置下,即使仅仅是在试图执行一个写操作之前,主分片都会要求 必须要有 规定数量(quorum))的分片副本处于活跃可用状态,才会去执行写操作 (其中分片副本可以是主分片或者副本分片)。

  • 这是为了避免在发生网络分区故障(network partition)的时候进行 写操作,进而导致数据不一致。
  • 规定数量即:
    i n t ( ( p r i m a r y + n u m b e r _ o f _ r e p l i c a s ) / 2 ) + 1 int( (primary + number\_of\_replicas) / 2 ) + 1 int((primary+number_of_replicas)/2)+1
    consistency 参数的值可以设为
    • one : 要主分片状态 ok 就允许执行_写_操作
    • all:必须要主分片和所有副本分片的状态没问题才允许执行写操作
    • quorum:默认值为 quorum , 即大多数的分片副本状态没问题就允许执行写操作。

      注意,规定数量 的计算公式中 number_of_replicas 指的是在索引设置中的设定副本分片数,而不是指当前处理活动状态的副本分片数。
      例如:如果你的索引设置中指定了当前索引拥有三个副本分片,那规定数量的计算结果即:int( (primary + 3 ) / 2 ) + 1 = 3
      如果此时你只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,也因此您将无法索引和删除任何文档。

参数:timeout

  • 如果没有足够的副本分片会发生什么? Elasticsearch 会等待,希望更多的分片出现。
  • 默认情况下,它最多等待 1 分钟。 如果你需要,你可以使用 timeout 参数使它更早终止: 100 100 毫秒,30s 是 30 秒

注意: 新索引默认有 1 个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当 number_of_replicas 大于 1 的时候,规定数量才会执行。

读流程

我们可以从主分片或者从其它任意副本分片检索文档读操作既可以在主分片中检索又可以在副分片中检索
在这里插入图片描述
从主分片或者副本分片检索文档的步骤顺序:

  1. 客户端向 Node 1 发送获取请求。
  2. 节点使用文档的 _id 来确定文档属于 分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2 。
  3. Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端。

在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。

更新流程

部分更新一个文档结合了先前说明的读取和写入流程:
在这里插入图片描述

  1. 客户端向 Node 1 发送更新请求。
  2. 它将请求转发到主分片所在的 Node 3 。
  3. Node 3 从主分片检索文档,修改 _source 字段中的 JSON ,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤 3 ,超过retry_on_conflict次后放弃。 控制事务,应该有一个标记位进行判定
  4. 如果 Node 3 成功地更新文档,它将新版本的文档并行转发到 Node 1 和 Node 2 上的副本分片,重新建立索引。一旦所有副本分片都返回成功, Node 3 向协调节点也返回成功,协调节点向客户端返回成功。

当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本
请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果 Elasticsearch 仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。
简单的说就是,更新是异步发出,不能保证到达顺序。如果只是转发更新请求很容易造成副本读取主分片的时序错误。

多文档操作流程

mgetbulk API 的模式类似于单文档模式。

  • 区别在于协调节点知道每个文档存在于哪个分片中。
  • 它将整个多文档请求分解成 每个分片 的多文档请求,并且将这些请求并行转发到每个参与节点。
  • 协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返 回给客户端

单个 mget 请求

在这里插入图片描述
用单个 mget 请求取回多个文档所需的步骤顺序:

  • 1、客户端向 Node 1 发送 mget 请求。
  • 2、Node 1 为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复, Node 1 构建响应并将其返回给客户端。

可以对 docs 数组中每个文档设置 routing 参数。

bulk API

bulk API, 允许在单个批量请求中执行多个创建、索引、删除和更新请求。

在这里插入图片描述
bulk API 按如下步骤顺序执行:

  • 客户端向 Node 1 发送 bulk 请求。
  • Node 1 为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。
  • 主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。
  • 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。

分片原理

分片是 Elasticsearch 最小的工作单元。但是究竟什么是一个分片,它是如何工作的?

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

倒排索引

  • Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。
  • 有倒排索引,肯定会对应有正向索引,正向索引就是搜索引擎会将待搜索的文件都对应一个文件 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 ,我们只需要查找包含每个词条的文档:
在这里插入图片描述

倒排索引的不变性

早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。

  • 倒排索引被写入磁盘后是 不可改变 的:它永远不会修改

不变性有重要的价值:

  • 不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
  • 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
  • 其它缓存(像 filter 缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
  • 写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量。

当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。 这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。

动态更新索引

按段搜索

如何在保留不变性的前提下实现倒排索引的更新?

答案是: 用更多的索引。 通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。

  • 每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。
  • Elasticsearch 基于 Lucene, 这个 java 库引入了按段搜索的概念。
  • 每一 段本身都是一个倒排索引, 但索引在 Lucene 中除表示所有段的集合外, 还增加了提交点的概念 (一个列出了所有已知段的文件)
    在这里插入图片描述

document 的索引增加过程

在这里插入图片描述

文档被索引的过程如上面所示,大致可以分为 内存缓冲区 buffer、translog、filesystem cache、系统磁盘 这几个部分,接下来我们梳理一下这个过程:

  • 1、更新到缓存: 这个阶段很简单,一个 document 文档第一步会同时被写进内存缓冲区 buffer 和 translog。
  • 2、refresh到cache:内存缓冲区的documents每隔一秒会被refresh(刷新)到 filesystem cache 中的一个新的segment中,segment就是索引的最小单位,此时segment将会被打开供检索。也就是说一旦文档被刷新到文件系统缓存中,其就能被检索使用了。这也是es近实时性(NRT)的关键。后面会详细介绍。
  • 3、merge:每秒都会有新的segment生成,这将意味着用不了多久segment的数量就会爆炸,每个段都将十分消耗文件句柄、内存、和cpu资源。这将是系统无法忍受的,所以这时,我们急需将零散的segment进行合并。ES通过后台合并段解决这个问题。小段被合并成大段,再合并成更大的段。然后将新的segment打开供搜索,旧的segment删除。
  • 4、更新到系统磁盘 :经过阶段 3 合并后新生成的更大的segment将会被flush到系统磁盘上。这样整个过程就完成了。

    但是这里留一个包袱就是flush的时机。在后面介绍 translog 的时候会介绍。

分段索引处理过期数据

  • 当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引。
  • 段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息
  • 当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。

合并段(merge) 的过程如下:

  • 一些有相似大小的segment,merge成一个大的segment
  • 将新的 segment flush 到磁盘上去
  • 写一个新的 commit point,包括了新的 segment,并且排除旧的那些segment
  • 将新的segment打开供搜索
  • 将旧的segment删除

近实时化搜索(NRT)

在早起的 lucene 中,只有当 segement被写入到磁盘,该segment才会被打开供搜索

  • 和我们上面所说的当 doc 被刷新到 filesystem cache 中生成新的 segment 就将会被打开。
  • 因为 per-segment search 机制,索引和搜索一个文档之间是有延迟的。新的文档会在几分钟内可以搜索,但是这依然不够快。磁盘是瓶颈。
  • 提交一个新的段到磁盘需要 fsync 操作,确保段被物理地写入磁盘,即时电源失效也不会丢失数据。但是 fsync 是昂贵的,它不能在每个文档被索引的时就触发。
  • 所以需要一种更轻量级的方式使新的文档可以被搜索,这意味这移除 fsync(同步) 。

位于Elasticsearch和磁盘间的是文件系统缓存。如前所说,在内存索引缓存中的文档被写入新的段,但是新的段首先写入文件系统缓存,这代价很低,之后会被同步到磁盘,这个代价很大。

  • 但是一旦一个文件被缓存,它也可以被打开和读取,就像其他文件一样。
  • 在 es 中每隔一秒写入内存缓冲区的文档就会被刷新到 filesystem cache 中的新的 segment,也就意味着可以被搜索了。这就是 ES 的 NR T——近实时性搜索。文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。

相关API

  • 如果你遇到过你新增了doc,但是没检索到,很可能是因为还未自动进行refresh,这时你可以尝试手动刷新:
    {
        "settings": {
        "refresh_interval": "30s" 
        }
    }
    
  • 如果想延长自动刷新时间,可以使用:
    {
    	 "settings": {
    	 "refresh_interval": "30s" 
    	 }
    }
    
  • 在生产环境中,当建立了一个大的新索引时,可以先关闭自动刷新,待开始使用时,再把它们调回来:
    # 关闭自动刷新
    PUT /users/_settings
    { "refresh_interval": -1 } 
    # 每一秒刷新
    PUT /users/_settings
    { "refresh_interval": "1s" }
    

持久化变更: translog 的使用

如果没有用 fsync 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。

  • 为了保证 Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。
  • 在动态更新索引,我们说一次完整的提交会将段刷到磁盘,并写入一个包含所有段列表的提交点
  • Elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。

即使通过每秒刷新(refresh)实现了近实时搜索,我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办?

  • 我们也不希望丢失掉这些数据。Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录 理解为 mysql主从复制中的log文件

整个流程如下所示:

  • 1、 一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog

  • 2、刷新(refresh)使分片每秒被刷新(refresh)一次:

    • 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。
    • 这个段被打开,使其可被搜索
    • 内存缓冲区被清空
      在这里插入图片描述
  • 3、这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志
    在这里插入图片描述

  • 4、 每隔一段时间,例如 translog 变得越来越大—索引被刷(flush);一个新的 translog 被创建,并且一个全量提交被执行

    • 所有在内存缓冲区的文档都被写入一个新的段。
    • 缓冲区被清空。
    • 一个提交点被写入硬盘。
    • 文件系统缓存通过 fsync 被刷新(flush)。
    • 老的 translog 被删除。
      在这里插入图片描述
  • 总结: 可以理解为总量备份,达到阈值才刷新

translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放translog中所有在最后一次提交后发生的变更操作。

  • translog 也被用来提供实时 CRUD 。当你试着通过 ID 查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更
  • 这意味着它总是能够实时地获取到文档的最新版本。
  • 执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush
  • 分片每 30 分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值