繁体   English   中英

分布式系统:Leader选举

[英]Distributed System: Leader Election

我目前正在研究分布式系统,我们必须在其中实施某种领导人选举 问题是我们希望避免所有计算机都必须相互认识——但只有领导者。 有没有一种快速的方法可以让我们使用例如 Broadcast 来实现我们想要的?

或者我们只需要知道至少一个,才能进行一次好的领导人选举?

假设所有计算机都在同一个子网上。

谢谢你的帮助。

问题是我们希望避免所有计算机都必须相互认识——但只有领导者。

领导选举是从一组潜在的领导候选人中选出一个领导的问题。 将其视为具有两个必需属性: livenesssafety 在这里,活跃意味着“大多数时候,有一个领导者”,而安全意味着“有零个或一个领导者”。 让我们考虑如何在您的示例中使用广播解决此安全属性。

让我们选择一个简单的(损坏的)算法,假设每个节点都有一个唯一的 ID。 每个节点广播其 ID 并进行侦听。 当收到比自己更高的 ID 时,它停止参与。 如果它收到比自己低的 ID,它会再次发送自己的广播。 假设一个同步网络,每个人收到的最后一个 ID 是领导者的 ID。 现在,介绍一个网络分区。 该协议将在分区的任一侧愉快地继续,并且将选出两名领导者。

这个被破坏的协议确实如此,但对于所有可能的协议也是如此。 如果您不知道(至少)存在多少个节点,您如何区分无法与之通信的节点和不存在的节点? 所以有第一个安全结果:你需要知道存在多少个节点,否则你不能确保只有一个领导者。

现在,让我们将安全约束放宽为概率约束:“可以有零个或多个领导者,但大多数时候只有一个”。 这使得问题易于处理,广泛使用的解决方案是八卦(流行病协议)。 例如,请参阅讨论这个确切问题的变体的八卦式故障检测服务 这篇论文主要关注概率正确的故障检测和枚举,但如果你能做到这一点,你也可以进行概率正确的领导选举。

据我所知,如果不枚举参与者,就不能在一般网络中进行安全的非概率领导者选举。

作为我上次看到的有趣的“分布式机制”解决方案之一,我推荐 Apache zookeeper项目。 这是开源解决方案,因此至少您应该能够从那里获得一些想法。 此外,它正在集中开发,因此您可能可以将其作为解决方案的一部分进行重用。

ZooKeeper 是一个集中式服务,用于维护配置信息、命名、提供分布式同步和提供组服务。 分布式应用程序以某种形式使用所有这些类型的服务。 每次实现它们时,都需要进行大量工作来修复不可避免的错误和竞争条件。 由于实现这些服务的难度,应用程序最初通常会吝啬它们,这使得它们在发生变化时变得脆弱且难以管理。 即使正确完成,在部署应用程序时,这些服务的不同实现也会导致管理复杂性。

我会推荐 JGroups 来解决这个问题——假设你正在 JVM 之上构建一个系统。

http://www.jgroups.org/

使用 LockService 确保集群中只有 1 个节点是领导者。 JGroups 可以设置为使用 Peer Lock 或 Central Lock - 两者都适用于您的情况。

有关 Clojure 实现,请参阅http://withmeta.blogspot.com/2014/01/leader-election-problem-in-elastic.html ,或http://javabender.blogspot.com.au/2012/01/jgroups-用于 Java 的lockservice-example.html

一个实用的解决方案是使用 DB 作为“会面”点。

这个解决方案非常方便,特别是如果您已经在使用 SQL DB,只需要一个新表。 如果您使用的是数据库集群,则可以利用其高可用性。

这是我的实现使用的表格:

CREATE TABLE Lease (
  ResourceId varchar(64),
  Expiration datetime,
  OwnerId varchar(64),
  PRIMARY KEY(ResourceId)
);

这个想法是每个共享资源都有一行。 领导者将争夺同一行。

我过度简化的 C# 实现如下所示:

class SqlLease {
  private ISqlLeaseDal _dal;
  private string _resourceId;
  private string _myId;

  public SqlLease(ISqlLeaseDal dal, string resourceId) {
    _dal = dal;
    _resourceId = resourceId;
    _myId = Guid.NewGuid().ToString();
  }

  class LeaseRow {
      public string ResourceId {get; set;}
      public string OwnerId {get; set;}
      public Datetime Expiration {get; set;}
      public byte[] RowVersion {get; set;}
  }

  public bool TryAcquire(Datetime expiration) {
    expiration = expiration.ToUniversalTime();
    if (expiration < DateTime.UtcNow) return false;
    try {
      var row = _dal.FindRow(_resourceId);
      if (row != null) {
        if (row.Expiration >= DateTime.UtcNow && row.OwnerId != _myId) {
          return false;
        }
        row.OwnerId = _myId;
        row.Expiration = expiration;
        _dal.Update(row);
        return true;
      }
      _dal.Insert(new LeaseRow {
        ResourceId = _resourceId,
        OwnerId = _myId,
        Expiration = expiration,
      });
      return true;
    } catch (SqlException e) {
      if (e.Number == 2601 || e.Number == 2627) return false;
      throw e;
    } catch (DBConcurrencyException) {
      return false;
    }
  }
}

ISqlLeaseDal类封装了 SQL 连接和对表的低级访问。

使用合理的截止日期。 请记住,如果当前领导者失败,资源将被锁定,直到到期结束。

@Marc 已经很好地描述了它。 我想补充几点。

如果所有参与系统必须不知道彼此,则广播 ID(或说时间戳)不会显示其状态,除非它被选为领导者。 一旦被选为领导者,它现在可以向集群中的所有其他节点广播机器的状态以进行连接。

如果参与的系统根本不能透露它们的存在,那么必须有一个系统来通信,例如一个数据库(如 Igor 提到的)、一个基于 TCP 的系统或一个安装位置(动物园管理员选择的方式)所有机器状态都是存储但最少的(或第一个具有读取权限的可用)并且领导者不断将其状态更新到该系统。 如果领导者宕机,则系统通过使其可供其他节点读取来选择下一个节点作为领导者,从而清理最后一个领导者条目。

Zookeeper 创建一个临时节点,可供所有节点读取。 这种行为可以通过在集群状态发生变化时仅使顶部节点可供读取来覆盖。

仅当大量节点同时启动(以毫秒为单位)并且中间系统花费太长时间无法返回微不足道的结果时,并发性才会成为问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM