Orleans 中的群集管理

Orleans 通过内置成员身份协议(有时称为 群集成员身份)提供群集管理。 该协议的目标是让所有接收器(Orleans 服务器)就一组当前存活接收器达成一致,检测失败的接收器,并允许新接收器加入群集。

该协议依赖于外部服务来提供 IMembershipTable 的抽象。 IMembershipTable 是一种平坦、耐用的桌子,用于两个用途。 首先,它充当仓库之间互相查找以及Orleans客户端查找仓库的集合点。 其次,它存储当前成员身份视图(活跃的节点列表),并帮助协调达成此视图的共识。

目前,有几个实现 IMembershipTable:基于 Azure 表存储Azure Cosmos DB、ADO.NET(PostgreSQL、MySQL/MariaDB、SQL Server、Oracle)、 Apache ZooKeeperConsul IOAWS DynamoDBMongoDBRedisApache Cassandra 和内存中实现进行开发。

此外 IMembershipTable,每个孤岛还参与一个完全分布式对等成员身份协议,该协议可检测失败的孤岛,并就一组活生生的孤岛达成协议。 Orleans的成员身份协议的内部实现如下所述。

成员身份协议

  1. 启动时,每个孤岛都会使用实现 IMembershipTable将条目添加到已知共享表中。 Orleans 将孤岛标识(ip:port:epoch)和服务部署 ID(群集 ID)组合用作表中的唯一键。 Epoch 只是此孤岛启动的时间,确保在 ip:port:epoch 给定 Orleans 的部署中是唯一的。

  2. 孤岛通过应用程序探测直接监视彼此(“你还活着了” heartbeats)。 探测通过用于常规通信的同一 TCP 套接字作为从孤岛到接收器的直接消息发送。 这样,探测与实际的网络问题和服务器运行状况完全关联。 每个 silo 都会对一组可配置的其他 silo 进行探测操作。 通过计算其他节点标识上的一致哈希值来选择需要探测的对象,形成所有节点标识的虚拟环,然后在环上挑选X个后续节点作为探测对象。 (这是一种称为 一致哈希 的已知分布式技术,在许多分布式哈希表中(如 Chord DHT)中广泛使用。

  3. 如果存储单元 S 未收到来自受监视服务器 P 的 Y 个探测回复,它会通过将带有时间戳的怀疑写入 P 的行内IMembershipTable来记录对 P 的怀疑。

  4. 如果 P 在 K 秒内被怀疑超过 Z 次,S 将 P 被确认死亡的记录写入 P 的数据行,并将当前的成员表快照广播到所有其他隔离组件。 孤岛会定期刷新表,因此快照是一种优化,以减少所有孤岛了解新成员身份视图所需的时间。

  5. 更详细地说:

    1. 怀疑被写入 IMembershipTable到与 P 对应的行中的一个特殊列中。当 S 嫌疑人 P 时,它写道:“当时 TTT S 疑似 P”。

    2. 一个怀疑不足以宣布P死。 需要在可配置的时间窗口 T(通常为 3 分钟)内来自不同信息孤岛的 Z 信号来确认 P 死亡。 怀疑是使用 IMembershipTable 提供的乐观并发控制写入的。

    3. 怀疑方 silo S 读取 P 的行。

    4. 如果 S 最后一名嫌疑人(在 T 期间内已有 Z-1 嫌疑人,如怀疑列中记录),S 决定宣布 P 死亡。 在这种情况下,S 会将自身添加到怀疑方列表中,并在 P 的状态列中写入“P 已消亡”。

    5. 否则,如果 S 不是最后一个怀疑者,则 S 只会将自身添加到嫌疑人的列中。

    6. 无论哪种情况,回写都使用以前读取的版本号或 ETag,序列化对此行的更新。 如果写入由于版本/ETag 不匹配而失败,S 重试(再次读取并尝试写入,除非 P 已标记为死)。

    7. 总体来说,这个“读取、本地修改、写回”的序列是一个事务处理。 然而,存储事务不一定会被使用。 “事务”代码在服务器上本地执行,并且由 IMembershipTable 提供的乐观并发确保隔离性和原子性。

  6. 每个 silo 定期读取整个成员身份表来进行部署。 这样,孤岛就了解新的孤岛加入,以及宣布死亡的其他孤岛。

  7. 快照广播:为了降低定期表读取的频率,每当一个孤岛写入表(例如怀疑、新加入等),它都会向所有其他孤岛发送当前表状态的快照。 由于成员身份表是一致的,并且版本是单调的,因此每次更新都会生成一个可以安全共享的唯一版本快照。 这样就可以立即传播成员身份更改,而无需等待定期读取周期。 在快照分发失败的情况下,定期读取仍被作为备份机制进行维护。

  8. 有序成员身份视图:成员身份协议可确保全局完全排序所有成员身份配置。 此排序提供两个关键优势:

    1. 连接性保证:当新的仓加入集群时,它必须验证与其他每个活动仓的双向连接。 如果任何现有的孤岛没有响应(这可能表示网络连接问题),就不允许新孤岛加入。 这可确保在启动时群集中的所有孤岛之间完全连接。 有关灾难恢复场景中的例外,请参阅以下说明 IAmAlive

    2. 一致的目录更新:高级协议(如分布式粒度目录)依赖于具有一致、单调成员身份视图的所有孤岛。 这样就可以更智能地解析重复的粒度激活。 有关详细信息,请参阅 Grain 目录 文档。

    实现的详细信息

    1. IMembershipTable 需要原子更新才能保证更改的全局总顺序:

      • 实现必须以原子方式更新表项(孤岛列表)和版本号。
      • 通过使用数据库事务(如在 SQL Server 中)或使用 ETag 的原子比较和交换操作(如在 Azure 表存储中)来实现这一目标。
      • 特定机制取决于基础存储系统的功能。
    2. 表中的特殊会员版本行用于跟踪更改:

      • 每次对表进行写入操作(可疑更改、删除声明、合并)都会递增此版本号。
      • 所有写入都使用原子更新通过此行进行序列化。
      • 单调递增的版本可确保所有成员变更的总顺序。
    3. 当接收器 S 更新接收器 P 的状态时:

      • S 首先读取最新的表状态。
      • 在单个原子操作中,它同时更新 P 的行并递增版本号。
      • 如果原子更新失败(例如,由于并发修改),操作会重试并进行指数级退避。

    可伸缩性注意事项

    由于争用增加,通过版本行序列化所有写入可能会影响可伸缩性。 该协议在生产过程中已被证明在多达200个筒仓的情况下有效,但在超过一千个筒仓时可能会面临挑战。 对于非常大的部署,即使成员身份更新成为瓶颈,Orleans(消息传递、粒度目录、托管)的其他部分仍可缩放。

  9. 默认配置:在 Azure 的生产使用期间,已手动调整默认配置。 默认情况下:每个筒仓都由另外三个筒仓监视,两个怀疑足以宣布某个筒仓失效,且只有过去三分钟内的怀疑才被考虑(否则它们已过时)。 探测信号每 10 秒发送一次,如果连续错过三个探测信号,则可能怀疑有发射仓问题。

  10. 自我监视:故障检测器整合了 HashiCorp 的 Lifeguard 研究(论文讲座博客)中的想法,以在灾难性事件期间,当群集中的很大一部分经历部分失效时,提高群集稳定性。 LocalSiloHealthMonitor 组件使用多个启发式方法对每个 silo 的运行状况进行评分:

    • 成员身份表中的活动状态
    • 没有来自其他 silo 的怀疑
    • 最近成功的探测响应
    • 最近收到的探测请求
    • 线程池响应能力(在 1 秒内执行的工作项)
    • 计时器准确性(在计划的 3 秒内触发)

    silo 的运行状况分数会影响其探测超时:与运行正常的 silo(评分 0)相比,运行不正常的 silo(评分 1-8)的超时时间增加。 这提供了两个优势:

    • 当网络或系统承受压力时,为探测提供更多时间才能成功。
    • 使不健康的孤岛更有可能被投票死亡,然后他们才能错误地投票出健康的孤岛。

    这在线程池资源不足等方案中尤其有价值;在这种情况下,慢速节点可能会因为无法足够快地处理响应而错误地怀疑正常节点。

  11. 间接探测:另一个 受 Lifeguard 启发的功能,通过减少不正常或被分区的仓错误地将健康仓宣告为故障的可能性,提高故障检测的准确性。 当一个监视 silo 在投票宣布其消亡之前,对目标 silo 还有两次探测尝试时,它会采用间接探测:

    • 监控筒仓随机选择另一个筒仓作为中介,并要求它探测目标。
    • 中介尝试联系目标筒仓。
    • 如果目标在超时期限内未能响应,中介会发送负确认。
    • 如果监控孤岛收到中介的否定确认,并且中介声明自己正常(通过上述自我监视),则监控孤岛将投票决定宣布目标死亡。
    • 由于默认配置为两个必需投票,间接探测中的负面确认被视为两票,这样当从多个角度确认故障时,可以更快地宣布库失效。
  12. 强制执行完美的故障检测:一旦在表中声明孤岛已死亡,每个人都认为它已死亡,即使它并没有真正死亡(例如,只是暂时分区或检测信号消息丢失)。 每个人都停止与它沟通。 一旦孤岛得知它已死(通过从表中读取其新状态),它就会终止其进程。 因此,必须建立基础设施以便将存储筒仓重启为新进程(在启动时生成新的纪元编号)。 在 Azure 中托管时,会自动发生这种情况。 否则,需要另一个基础结构,例如配置为在故障或 Kubernetes 部署时自动重启的 Windows 服务。

  13. 如果表在一段时间内无法访问,会发生什么情况

    当存储服务关闭、不可用或遇到通信问题时, Orleans 协议不会错误地声明孤岛死亡。 运营孤岛在没有任何问题的情况下继续运作。 但是,Orleans 无法声明一个筒仓已经失效(如果它通过错过的探测检测到死筒仓,它就无法将这一事实写入表格),也无法启用新筒仓加入。 因此,完整性会受到影响,但准确性不会—从表中分区永远不会导致 Orleans 错误地声明孤岛死亡。 此外,在部分网络隔离的情况下(其中一些节点可以访问表,而另一些节点无法 Orleans 访问),可能会判断某个节点失效,但所有其他节点需要时间才能察觉到这一点。 检测可能会延迟,但由于表不可用,Orleans 永远不会错误地终止筒仓。

  14. IAmAlive 为诊断和灾难恢复撰写文章

    除了孤岛之间发送的检测信号外,每个孤岛还会定期在其表行中更新“我处于活动状态”时间戳。 这有两个用途:

    1. 诊断:为系统管理员提供一种简单的方法来检查群集的存活性,并确定Silo上次处于活动状态的时间。 时间戳通常每 5 分钟更新一次。

    2. 灾难恢复:如果一个silo在几个时间段内未更新其时间戳(通过NumMissedTableIAmAliveLimit配置),则新的silo会在启动时的连接检查期间忽略它。 这样可以帮助群集在孤岛崩溃而未进行正确清理的情况下进行恢复。

成员身份表

如前所述, IMembershipTable 作为孤岛相互查找的交会点,供 Orleans 客户端找到孤岛。 它还有助于协调关于成员资格观点的协议。 主 Orleans 存储库包含许多系统的实现,包括 Azure 表存储、Azure Cosmos DB、PostgreSQL、MySQL/MariaDB、SQL Server、Apache ZooKeeper、Consul IO、Apache Cassandra、MongoDB、Redis、AWS DynamoDB 以及用于开发的内存中实现。

  1. Azure 表存储:在此实现中,Azure 部署 ID 用作分区键,孤立体标识(ip:port:epoch)用作行键。 它们一起保证每个孤岛的唯一密钥。 对于并发控制,使用基于 Azure 表 ETag 的 乐观并发控制。 每次从表中读取数据时,都会存储每个读取行的 ETag,并在尝试写回时使用。 Azure 表服务会在每次写入时自动分配和检查 ETag。 对于多个行的事务,利用 Azure 表提供的批处理事务支持,确保对具有相同分区键的行进行可序列化事务。

  2. SQL Server:在此实现中,配置的部署 ID 用于区分不同的部署以及哪些孤岛属于哪些部署。 silo 标识定义为相应表和列中 deploymentID, ip, port, epoch 的组合。 关系后端使用乐观并发控制和事务,类似于在 Azure 表实现中使用 ETag。 关系实现需要数据库引擎生成 ETag。 对于 SQL Server 2000,生成的 ETag 是从调用NEWID()中获取的。 在 SQL Server 2005 及更高版本中,使用 ROWVERSION 。 Orleans 以不透明的 VARBINARY(16) 标记形式读取和写入关系 ETag,并将其以 base64 编码字符串的形式存储在内存中。 Orleans 支持使用 UNION ALL(包括 Oracle)的多行插入, DUAL 目前用于插入统计数据。 SQL Server 的确切实现和理由在 CreateOrleansTables_SqlServer.sql提供。

  3. Apache ZooKeeper:在此实现中,配置的部署 ID 作为根节点,而孤立标识(ip:port@epoch)作为其子节点。 它们一起确保每个筒仓有唯一路径。 对于并发控制,采用基于节点版本的乐观并发控制。 每次从部署根节点读取数据时,都会存储每个读取子槽节点的版本,并在尝试将数据写回时使用。 每次节点的数据更改时,ZooKeeper 服务都会原子地增加版本号。 对于多行事务,使用多行事务处理方法,以保证同一父部署 ID 节点的沙箱节点之间的事务可序列化。

  4. Consul IO:Consul 的键/值存储用于实现成员列表。 有关更多详细信息 ,请参阅 Consul Deployment

  5. AWS DynamoDB:在此实现中,群集部署ID用作分区键,Silo身份(ip-port-generation)用作排序键,使记录唯一。 使用ETag属性进行条件写入可以在DynamoDB中实现乐观并发。 实现逻辑与 Azure 表存储非常相似。

  6. Apache Cassandra:在此实现中,服务 ID 和群集 ID 的复合用作分区键,孤岛标识(ip:port:epoch)作为行键。 它们一起保证每个仓库的唯一路径。 对于并发控制,使用基于静态列版本和轻量级事务的乐观并发控制。 此版本列针对分区/群集中的所有行共享,为每个群集的成员身份表提供一致的递增版本号。 此实现中没有多行事务。

  7. 用于开发设置的内存中仿真:此实现使用了一个特殊的系统模块。 此 grain 驻留在某个指定的主要 silo 上,该 silo 仅用于开发设置。 在任何实际生产使用情况下,不需要主孤立单元。

设计理由

一个自然的问题可能是,为什么不完全依赖 Apache ZooKeeperetcd 来实现集群成员身份,并且利用 ZooKeeper 开箱即用的临时节点组成员身份支持? 为什么推行我们的会员制度? 主要有三个原因:

  1. 在云中部署/托管:

    Zookeeper 不是托管服务。 这意味着在云环境中, Orleans 客户必须部署、运行和管理其 ZK 群集实例。 这是一个不必要的负担,并不是客户被强加的。 通过使用 Azure 表,Orleans 依赖于托管服务,使客户的生活变得更加简单。 基本上,在云中,使用云作为平台,而不是基础结构。 另一方面,在本地运行并管理服务器时,依靠 ZK 来实现 IMembershipTable 是一个可行的选择。

  2. 直接故障检测:

    将 ZK 的组成员身份与临时节点一起使用时,服务器(ZK 客户端)和 ZK 服务器之间 Orleans 会发生故障检测。 这不一定与服务器之间的 Orleans 实际网络问题相关联。 希望故障检测准确反映通信的群集内部状态。 具体而言,在此设计中,如果 Orleans 孤岛无法与之 IMembershipTable通信,则它不被视为死,可以继续工作。 相比之下,如果使用了具有临时节点的 ZK 组成员身份,则与 ZK 服务器的断开连接可能会导致 Orleans 孤立节点(ZK 客户端)被声明为死机,而实际上它可能是活跃并且功能完全正常。

  3. 可移植性和灵活性:

    作为Orleans理念的一部分,Orleans不强制对任何特定技术有强依赖,而是提供灵活的设计,不同的组件可以轻松与不同的实现进行切换。 这正是 IMembershipTable 抽象所服务的目的。

成员身份协议的属性

  1. 可以处理任意数量的故障:

    此算法可以处理任意数量的故障(f<=n),包括完全群集重启。 这与基于Paxos的“传统”解决方案形成鲜明对比,这些解决方案需要法定人数(通常是多数)。 生产状况显示,超过一半的筒仓出现故障的情况。 该系统保持正常运行,而使用 Paxos 协议的成员系统将无法取得进展。

  2. 发送到表的流量很少:

    实际探测直接在服务器之间进行,而不是到某个表。 通过表的路由探针将产生大量流量,从故障检测的角度来看,这种方法的准确性较低——如果一个孤岛无法到达表,它将无法记录其存活信号,其他结构将判断它已失效。

  3. 可调准确度与完整性:

    虽然无法实现完美和准确的故障检测,但通常希望能够权衡准确性(不想声明活孤岛死亡)与完整性(希望尽快声明死岛死亡)。 可配置的投票可以决定如何处理“探测失效”和“丢失探测”这两个方面的平衡。 有关详细信息,请参阅耶鲁大学文章 Computer Science Failure Detectors(计算机科学故障检测器)。

  4. 规模:

    协议可以处理数千台甚至数万台服务器。 这与传统的基于 Paxos 的解决方案(如组通信协议)形成鲜明对比,这些协议已知不会扩展到数十个节点之外。

  5. 诊断:

    该表还能够为诊断和故障排除提供方便。 系统管理员可以立即在表中找到当前活体孤岛列表,并查看所有被杀孤岛和怀疑的历史。 这些信息在诊断问题时特别有用。

  6. 实现以下各项需要IMembershipTable可靠的持久存储的原因

    永久性存储用于 IMembershipTable 两个目的。 首先,它充当节点相互查找以及Orleans客户端寻找节点的连接点。 其次,可靠存储有助于协调关于成员视图的协议。 虽然故障检测是在过道之间直接进行的,但成员视图存储在可靠的存储中,并使用此存储提供的并发控制机制来达成共识,确定哪些节点是活动的,哪些是静止的。 从某种意义上说,此协议将分布式共识的难题外包给云。 这样做时,将利用基础云平台的全部功能,将其真正用作平台即服务(PaaS)。

  7. 仅对诊断进行直接 IAmAlive 写入表

    除了在孤岛之间发送的心跳信号外,每个孤岛还会定期更新其数据表中的“I Am Alive”列。 此“我处于活动状态”列仅用于手动故障排除和诊断,成员协议本身不使用。 它通常以更低的频率(每 5 分钟一次)记录,并充当一种非常有用的工具,供系统管理员检查群集的存活性,或轻松查出孤立服务器上次处于活跃状态的时间。

致谢

感谢 Alex Kogan 在此协议第一个版本的设计和实现方面所做的贡献。 这项工作是作为 2011 年夏季 Microsoft Research 暑期实习的一部分完成的。 基于 IMembershipTable ZooKeeper 的实现由 Shay Hazor 完成,SQL IMembershipTable 的实现由 Veikko Eeva 完成,AWS DynamoDB IMembershipTable 的实施由 Gutemberg Ribeiro 完成,基于 IMembershipTable Consul 的实施由 Paul North 完成,最后 Apache Cassandra IMembershipTableOrleansCassandraUtils 的实施由 Arshia001 改编。