上一篇文章中, 我们讨论了群集组合、节点发现、主选择、错误检测、群集缩放等。本文将根据上一节重点分析 es 中元更新的一致性问题。为了增强我们的理解, 它还介绍了群集管理、元组合、存储和其他信息的主方法。我们将讨论以下议题:

  1. 主控管理群集的方式。
  2. 元组合、存储和恢复。
  3. 群集状态更新过程。
  4. 解决当前的一致性问题。
  5. 总结

主管理群集的方式

在上一篇文章中, 我们介绍了 es 集群组合、如何发现节点和主选择。那么, 在成功选择群集后, 主控如何管理集群呢?有几个问题需要解决, 例如:

  1. 主控如何处理索引的创建或删除?
  2. 如何主重新安排碎片的负载平衡?

由于需要群集管理, 因此主节点必须有某种方法来通知其他节点执行相应的操作来完成任务。例如, 在创建新索引时, 它必须将其 “碎片” 分配给某些节点。必须在节点上创建与 “碎片” 相对应的目录, 这意味着它必须创建与内存中的 “碎片” 相对应的数据结构。

在 es 中, 主节点通过发布群集状态通知其他节点。主节点将新的群集状态发布到所有其他节点。当这些节点接收到新的 cluclstate 时, 它们会将其发送到相关模块。然后, 这些模块根据新的 ClusterState 确定要执行的操作, 例如, 创建一个碎片。这是如何通过元数据协调每个模块的操作的示例。

当 master 进行元更改并通知所有其他节点时, 必须考虑元更改一致性问题。如果 master 在此过程中崩溃, 则只有某些节点可以根据新元执行操作。选择新的 master 时, 需要确保所有节点都按照新的元执行操作, 并且不能回滚。由于某些节点可能已经按照新元执行了操作, 因此如果发生回滚, 则会导致不一致问题。

在 es 中, 只要在节点上提交新元, 就会执行相应的操作。因此, 我们必须确保一旦在节点上提交了新的元, 无论哪个节点是主节点, 它都必须基于此提交生成更新的元;否则, 可能会发生不一致。本文分析了这一问题以及为解决这一问题所采取的策略。

元组合、存储和恢复

在介绍元更新过程之前, 我们将介绍元组合、存储和恢复。如果您熟悉此主题, 则可以直接跳到下一个主题。

1. meta:cl元态、metadata、索引 metadata

元数据是用于描述数据的数据。在 es 中, 索引映射结构、配置和持久性是元数据, 群集的某些配置信息也属于元数据。这样的元数据非常重要。如果记录索引的元数据丢失, 群集认为索引不再存在。在 es 中, 元数据只能由主数据更新, 因此主数据本质上是群集的大脑。

集群州

群集中的每个节点都在内存中维护当前的群集状态, 该内存指示当前群集中的各种状态。群集状态包含元数据结构。存储在元数据中的内容更符合元数据特征, 要保留的信息是元数据中的信息

群集状态包含以下信息:

    long version: current version number, which increments by 1 for every update
    String stateUUID: the unique id corresponding to the state
    RoutingTable routingTable: routing table for all indexes
    DiscoveryNodes nodes: current cluster nodes
    MetaData metaData: meta data of the cluster
    ClusterBlocks blocks: used to block some operations
    ImmutableOpenMap<String, Custom> customs: custom configuration
    ClusterName clusterName: cluster name

如上所述, 元数据更符合元特征, 必须保持, 因此, 接下来, 我们来看看主要包含元数据的内容。

必须在元数据中保留以下内容:

    String clusterUUID: the unique id of the cluster.
    long version: current version number, which increments by 1 for every update
    Settings persistentSettings: persistent cluster settings
    ImmutableOpenMap<String, IndexMetaData> indices: Meta of all Indexes
    ImmutableOpenMap<String, IndexTemplateMetaData> templates: Meta of all templates
    ImmutableOpenMap<String, Custom> customs: custom configuration

正如您所看到的, 元数据主要包括群集配置、群集中所有索引的元数据以及所有模板的元数据。接下来, 我们将对 IndexMetaData 其进行分析, 稍后还将对其进行讨论。虽然 IndexMetaData 它们也是其中的一部分 MetaData , 但它们是分开存储的。

索引 metadata

IndexMetaData指索引的元, 如分片计数、副本计数和索引的映射。必须在中保留以下 IndexMetaData 内容:

    long version: current version number, which increments by 1 for every update.
    int routingNumShards: used for routing shard count; it can only be the multiples of numberOfShards of this Index, which is used for split.
    State state: Index status, and it is an enum with the values  OPEN or CLOSE.
    Settings settings: configurations, such as numbersOfShards and numbersOfRepilicas.
    ImmutableOpenMap<String, MappingMetaData> mappings: mapping of the Index
    ImmutableOpenMap<String, Custom> customs: custom configuration.
    ImmutableOpenMap<String, AliasMetaData> aliases: alias
    long[] primaryTerms: primaryTerm increments by 1 whenever a Shard switches the Primary, used to maintain the order.
    ImmutableOpenIntMap<Set<String>> inSyncAllocationIds: represents the AllocationId at InSync state, and it is used to ensure data consistency, which is described in later articles.

2. 元存储

首先, 当启动 es 节点时, 将配置一个数据目录, 类似于以下内容。此节点只有单个碎片的索引。

$tree
.
`-- nodes
    `-- 0
        |-- _state
        |   |-- global-1.st
        |   `-- node-0.st
        |-- indices
        |   `-- 2Scrm6nuQOOxUN2ewtrNJw
        |       |-- 0
        |       |   |-- _state
        |       |   |   `-- state-0.st
        |       |   |-- index
        |       |   |   |-- segments_1
        |       |   |   `-- write.lock
        |       |   `-- translog
        |       |       |-- translog-1.tlog
        |       |       `-- translog.ckp
        |       `-- _state
        |           `-- state-2.st
        `-- node.lock

您可以看到 es 进程将元和数据写入此目录, 该目录 named _state 指示该目录存储元文件。根据文件级别, 有三种类型的元存储:

  • nodes/0/_state/:此目录位于节点级别

此目录下的第一个文件存储 MetaData 上面提到的内容, IndexMetaData 但部件除外, 该部分包括群集级别上的一些配置和模板。

  • nodes/0/indices/2Scrm6nuQOOxUN2ewtrNJw/_state/:此目录位于索引级别, 2scrm6noqoxun2ewtrnjw IndexId 是, 此目录下的状态 2. st 文件存储上述内容 IndexMetaData

  • nodes/0/indices/2Scrm6nuQOOxUN2ewtrNJw/0/_state/:此目录处于分片级别, 此目录存储下 ShardStateMetaData 的状态为 0. st, 其中包含 allocationId 诸如以及是否为主目录的信息 ShardStateMetaData . 在模块中进行管理 IndexShard , 这与其他元目录没有那么相关, 因此我们不会讨论详细信息这里。

  • 正如您所看到的, 与群集相关 MetaData 的和 IndexMetaData 存储在不同的目录中。此外, 与群集相关的元数据存储在所有 MasterNodes DataNodes IndexMeta , 而存储在所有 MasterNodesDataNodes 已存储此索引数据。

    这里的问题是 MetaData , 因为是由师父管理的, 为什么 MetaData 也存储在 DataNode 上面?这主要是为了数据安全: 许多用户不考虑主服务器和高数据可靠性的高可用性要求, 并且仅在 MasterNode 部署 es 群集时配置一个。在此情况下, 如果节点出现故障, 元可能会丢失, 这可能会对他们的业务产生巨大影响。

    3. 元恢复

    如果 es 群集重新启动, 则需要一个角色来恢复元数据, 因为所有进程都丢失了以前的元信息;这个角色是师父。首先, 主选举必须在 es 集群中进行, 然后可以开始故障恢复。

    选择 master 时, master 进程将等待满足特定条件, 例如群集中有足够的当前节点, 这可以防止由于某些数据节点断开连接而产生不必要的数据恢复情况。

    当 master 进程决定恢复元数据时, 它会将请求发送到 masternode 和 MetaData, 以获取其计算机上的 metadata。对于群集, 将选择具有最新版本号的元。同样, 对于每个索引, 将选择具有最新版本号的元。然后, 群集的元和每个索引组合在一起, 形成最新的元。

    群集状态更新过程

    现在, 我们将了解群集状态更新过程, 以了解 es 如何在基于此过程的群集状态更新过程中保证一致性。

    1. 主过程中不同线程所做的群集状态更改的原子性保证

    首先, 当不同的线程在主进程中更改 clusterstate 时, 必须保证原子性。假设有两个线程正在修改群集状态, 并且它们正在进行自己的更改。如果没有并发保护, 则最后一个线程提交提交的修改将覆盖第一个线程提交的修改或导致无效状态更改。

    若要解决此问题, es 在需要群集状态更新时将任务提交到 masterservice, 以便 masterservice 仅使用一个线程按顺序处理任务。当前 clusterstate 在处理任务时用作任务的执行函数的参数

    2. 确保随后的变化都是相应的承诺, 而不是在进行集群国家变化后收回

    如您所知, 一旦在节点上提交了新的 meta, 该节点将执行相应的操作, 如删除分片, 并且无法回滚这些操作。但是, 如果主节点在实现更改时崩溃, 则必须基于新的 meta 更改新生成的 master 节点, 并且不能发生回滚。否则, 尽管无法回滚操作, 但 meta 可能会回滚。实质上, 这意味着不再与元更新保持一致。

    在早期的 es 版本中, 此问题未得到解决。后来引入了两阶段提交 (添加对群集状态发布的两个阶段提交)。两阶段提交将发布群集状态进程的 master 拆分为两个步骤: 首先, 将最新的 clusterstate 发送到所有节点;其次, 一旦超过一半的主节点返回 ack, 提交请求将被发送, 要求节点提交接收到的群集状态。如果一半以上的节点未返回 ack, 则发布将失败。此外, 它将丢失主状态并执行重新联接以重新加入群集。

    两阶段提交可以解决一致性问题, 例如:

    1. 节点 a 最初是主节点, 但由于某种原因, 节点 b 成为新的主节点。由于心跳或检测问题, node a 没有发现更改。
    2. 因此, node a 仍然认为自己是大师, 并像往常一样发布新的群集状态。
    3. 但是, 由于节点 b 现在是主节点, 这表明超过一半的主节点认为节点是新的主节点, 因此它们不会返回 ack 到节点。
    4. 由于 node a 无法接收到足够的 ack, 因此发布将失败, 并且 node 将丢失主状态。
    5. 而且, 由于新的群集状态未在任何节点上提交, 因此不存在不一致之处。

    然而, 这种方法有许多不一致问题, 我们接下来将讨论这些问题。

    3. 一致性问题分析

    es 中的原则是, 如果超过一半的 masternode (主符合条件的节点) 接收到新的群集状态, 则主发送提交请求。如果一半以上的节点被认为已收到新的群集状态, 则肯定可以提交群集状态, 并且在任何情况下都不会回滚。

    第1期

    在第一阶段, 主节点发送一个新的 clusterstate, 接收它的节点只需在内存中排队, 然后返回 ack。此过程不会保留, 因此, 即使主节点接收到一半以上的 ack, 我们也不能假定新的 clusterstate 位于所有节点上。

    第2期

    如果在主提交阶段只提交几个节点, 并且将主节点和这些节点划分为同一个分区时发生网络分区, 则其他节点可以相互访问。此时, 其他节点占大多数, 并将选择一个新的主节点。由于这些节点都没有提交新的 clusterstate, 因此新的主机将继续使用更新前设置的 clusterstate, 从而导致元不一致。

    es 仍在跟踪此错误:

    https: //www.elastic.co/guide/en/elasticsearch/resiliency/current/index.html
    
    Repeated network partitions can cause cluster state updates to be lost (STATUS: ONGOING)
    ... This problem is mostly fixed by #20384 (v5.0.0), which takes committed cluster state updates into account during master election. This considerably reduces the chance of this rare problem occurring but does not fully mitigate it. If the second partition happens concurrently with a cluster state update and blocks the cluster state commit message from reaching a majority of nodes, it may be that the in flight update will be lost

    修复最后一种情况需要做大量的工作。我们目前正在研究这个问题, 但还没有 e t a。

    在什么情况下会出现问题?

    在两阶段提交中, 最重要的条件之一是 “如果包含一半以上的节点, 则返回 ack, 指示已收到此群集状态”, 这并不能保证任何情况。一方面, 接收集群国家不会持续存在;另一方面, 接收集群国家不会对未来的主选举产生任何影响, 因为只会考虑承诺的集群国家。

    在两阶段过程中, 只有当主机在一半以上的 masterode (符合主节点) 上提交新的群集状态, 并且这些节点都已完成新群集状态的持久性时, 主服务器才会处于安全状态。如果主机在提交开始和此状态之间有任何发送失败, 则可能会发生元不一致。

    解决现有的一致性问题

    由于 es 在元更新方面存在一些一致性问题, 因此这里有一些解决这些问题的方法。

    1. 实现标准化一致性算法, 如 raft

    第一种方法是实现标准化的一致性算法, 如木筏。在上一篇文章中, 我们解释了 es 选择算法和木筏算法之间的异同。现在, 我们将继续比较 es 元更新过程和木筏日志复制过程。

    相似 之 处:

    提交是在收到来自一半以上节点的响应后执行的。

    差异:

    1. 对于木筏算法, 跟随器将接收到的日志保留在磁盘上。对于 es, 节点接收 clucerstate, 将其放入内存中的队列中, 然后立即返回。群集状态不会持续。
    2. 木筏算法可确保在一半以上的节点响应后可以提交日志。这在 es 中不能保证, 因此可能会出现一些一致性问题。

    上面的比较显示了 es 和木筏的元更新算法的相似性。然而, 木筏有额外的机制来确保一致性, 而 es 有一些一致性问题。es 正式表示, 需要做大量工作来解决这些问题。

    2. 通过使用其他组件确保元一致性

    例如, 我们可以使用 zookeeper 保存元保存元并确保元一致性。这确实解决了一致性问题, 但性能仍需考虑。例如, 评估当元卷太大时, 元数据是否保存在 zookeeper 中, 或者是否每次都需要请求完整的元数据或差异数据。

    3. 使用共享存储保存元空间

    首先, 确保不会发生分裂大脑, 然后将元保存在共享存储中, 以避免任何一致性问题。这种方法需要高度可靠和可用的共享存储系统。

    总结

    作为本系列关于弹性搜索分布一致性原理的第二篇文章, 本文主要展示 es 集群中的主节点如何发布元更新并分析潜在的一致性问题。本文还介绍了元数据的组成以及元数据的存储方式。下面的文章介绍了用于确保 es 中数据一致性的方法, 以及数据编写过程和算法模型。

    引用

    Comments are closed.