Orleans 2.0 官方文档 —— 8.5 实现细节 -> 集群管理

Orleans的集群管理

Orleans通过内置的成员资格协议,提供了集群管理,我们有时将其称为Silo 成员资格 (Silo Membership)。该协议的目标是让所有silo(Orleans服务器)就当前活动的silo集合达成一致、检测故障silo,并允许新的silo加入集群。

该协议依赖于外部服务,来提供MembershipTable的抽象。MembershipTable是一个平面的(flat)、类似No-SQL的持久表,我们将其用于两个目的。首先,它被用作一个集合点,以便silo找到彼此和Orleans客户端找到silo。其次,它用于存储当前的成员资格的视图(活动的silo列表),并帮助协调在成员资格视图上达成一致。我们目前有6个MembershipTable的实现:基于Azure表存储、SQL服务器、Apache ZooKeeper、Consul IO、AWS DynamoDB和用于开发的内存仿真。除了MembershipTable外,每个silo都参与完全分布式的点对点成员协议,该协议可以检测出故障的silo,并在一组活动的silo上达成一致。我们首先描述下面Orleans成员资格协议的内部实现,然后描述该MembershipTable的实现。

基本的成员资格协议:

  1. 在启动时,每个silo都会将自己写入一个众所周知的MembershipTable(通过配置传递)。silo的标识(ip:port:epoch)和服务部署id,这二者的组合用作表中的唯一键。Epoch是这个silo启动时的时间(Ticks的格式),因此ip:port:epoch在给定的Orleans部署中保证是唯一的。

  2. silo通过应用程序的ping(“你还活着吗” 的心跳)来直接监控彼此。通过与silo通信相同的TCP套接字,ping作为直接消息从silo发送到silo。这样,ping与实际的网络问题和服务器健康状况完全相关。每个silo都会ping X个其他的silo。一个silo通过计算其他silo的标识的一致性哈希,来选择要ping的对象,形成一个所有标识的虚拟环,并在环上选择X个后继silo(这是一种众所周知的分布式技术,称为一致哈希,并广泛用于许多分布式哈希表,如Chord DHT)。

  3. 如果一个silo S没有收到来自受监控的服务器P的Y次ping的回复,它会通过将带时间戳的怀疑,写入到MembershipTable的P的行,来表示怀疑。

  4. 如果P在K秒内有超过Z次的怀疑,则S会将P已经死亡写入P的行,并广播一个请求,要去所有silo重新读取成员资格表(不管怎样,它们都会定期进行)。

  5. 更多细节:

    5.1怀疑被写入MembershipTable的对应于P的行的一个特殊列中。当S怀疑P时,它写道:“在TTT时间点,S怀疑了P”。

    5.2一个怀疑不足以宣布P为死亡。您需要在可配置的时间窗口T(通常为3分钟)内,将来自不同silo的Z次怀疑声明为已死亡。怀疑是使用MembershipTable提供的乐观并发控制来写入的。

    5.3处于怀疑中的silo S读取P的行。

     5.4 如果S是最后一个怀疑者(时间段T内已经有Z-1个怀疑者,如suspicion栏中所写),S决定宣布P为死亡。在这种情况下,S将自己添加到suspectors列表中,并在P的Status列中写入P已死。
    
     5.5 否则,如果S不是最后一个怀疑者, S仅仅是把它自己添加到suspectors栏位中。
    

    5.6在任何一种情况下,回写都使用读取的版本号或etag,因此对该行的更新都是序列化的。如果由于版本/etag不匹配导致写入失败,则重试S次(再次读取并尝试写入,除非P已经标记为死)。

    5.7在高层级上,这个“读取、本地修改、回写”序列是一个事务。但是,我们没有使用存储事务来执行此操作。“事务”代码在服务器上本地执行,我们使用MembershipTable提供的乐观并发,来确保隔离和原子性。

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

  7. 配置:我们提供了一个默认配置,它在Azure的生产使用期间可手动调整。目前默认设置是:每个silo由3个其他silo监控,2个怀疑足以宣告silo死亡,怀疑仅从最近的3分钟(否则它们已过时)。每隔10秒发送一次Ping,你需要丢失3次ping才能怀疑一个silo。

  8. 执行完善的故障检测 - 理论上有可能存在这种情况:silo进程本身仍在运行,但由于silo与其他silo失去联系,此silo被宣告死亡。为了解决这个问题,一旦silo在表中被宣布死亡,它就会被所有人认为是死的,即使它实际上并没有死亡(只是临时性分区或心跳消息丢失)。每个人都停止与它通信,一旦得知它已经死了(通过从表中读取它自己的新状态),它就会自杀并关闭它的进程。因此,必须有一个基础设施来重新启动silo作为新流程(在启动时生成新的Epoch)。当它在Azure中承载时,这种情况会自动发生。否则,就需要另一个基础设施。例如,配置为在失败时自动重新启动的Windows服务。

  9. 优化以减少周期性读取表的频率,并加速所有silo了解新加入的silo和死亡的silo。每当任何silo成功写入表(怀疑,新加入......)时,它也会向所有其他silo广播 - “现在就去重新读取表”。silo并没有告诉别人它在表中写了什么(因为这些信息可能已经过时/错误),它只是告诉他们重新读取表。这样,我们可以非常快速地了解成员资格的变化,而无需等待完整的周期性读取周期。如果“重新读取表”消息丢失,我们仍然需要周期性读取。

基本的成员资格协议的属性和FAQ:

  1. 可以处理任意数量的故障 - 我们的算法可以处理任意数量的故障(即f <= n),包括完全的集群重启。这与“传统的” 基于Paxos的解决方案形成了鲜明对比,后者需要法定人数,而法定人数通常占多数。在生产情况下,我们已经看到过超过一半的silo宕机。我们的系统保持正常运行,而基于Paxos的会员资格将无能为力。
  2. 到表的流量非常轻 - 实际的ping直接在服务器之间进行,而不是在表之间进行。从故障检测的角度来看,这会产生大量的流量加上准确性会降低 —— 如果一个silo无法到达表,它就会错过写入“我还活着”的心跳,则其他人会杀了他。
  3. 可调的精度与完整性 - 一般来说,完美和准确地进行故障检测是不可能的。人们通常希望能够权衡准确性(不希望宣布一个真正活着的silo是死的)和完整性(希望尽快宣布已经死亡的silo)。可配置的#votes(用来声明dead和#missed ping),允许的交换调整这两个要素。

  4. 规模 - 基本协议可以处理数千甚至数万台服务器。这与传统的基于Paxos的解决方案形成对比,例如群组通信协议,这些解决方案的规模不超过数十台。

  5. 诊断 - 该表也非常方便诊断和故障排除。系统管理员可以立即在表中找到当前活着的silo列表,以及查看所有被杀死的silo和怀疑的历史记录。这在诊断问题时特别有用。

  6. 为什么我们需要可靠的持久存储来实现MembershipTable - 我们使用持久存储(Azure表、SQL服务器、AWS DynamoDB、Apache ZooKeeper或Consul IO KV)作为MembershipTable有两个目的。首先,它被用作一个集合点,以便silo找到彼此和Orleans客户端找到silo。其次,我们使用可靠的存储,来帮助我们协调在成员资格视图上达成一致。虽然我们直接在silo之间以点对点的方式执行故障检测,但我们将成员资格视图存储在可靠的存储中,并使用此存储提供的并发控制机制,来达成谁活着和谁死的一致。这样,从某种意义上说,我们的协议将分布式共识的难题外包给云。在这一点上,我们充分利用了底层云平台的功能,将其真正用作“平台即服务”。

  7. 如果表暂时无法访问会怎样?(存储服务已关闭、不可用或存在通信问题) - 在这种情况下,我们的协议不会错误地声明silo为死。目前运行的silo将继续正常运行,不会出现任何问题。但是,我们将无法宣布一个silo死亡(如果我们通过丢失的ping检测到一些silo已死,我们将无法将此事实写入表中),并且也无法允许新的silo加入。因此,完整性将受到影响,但准确性不会 —— 从表中进行分区将永远不会导致我们错误地宣布silo死亡。此外,在部分网络分区的情况下(如果某些silo可以访问表,而有些不能访问表),可能会发生虽然我们将死亡的silo宣布了死亡,但是所有其他silo需要一些时间才能知道这一点。所以检测可能会延迟,但我们不会因为表不可用而误杀某个silo。

  8. 直接将IAmAlive写入表中,仅用于诊断 - 除了在silo之间发送的心跳之外,每个silo还定期更新表中它自己那一行的“I Am Alive”列。此“I Am Alive”列仅用于手动故障排除和诊断,成员资格协议本身不使用。它通常以更低的频率(每5分钟一次)写入,对于系统管理员来说,它是一个非常有用的工具,可以检查集群的活跃性,或者很容易地发现silo最后的活动时间。

扩展到全序的成员资格视图:

上面描述的基本的成员资格协议,后来被扩展为支持全序的成员资格视图。我们将简要介绍此扩展的原因及其实现方式。扩展不会改变上述设计中的任何内容,只是添加了一个额外的属性,即所有成员资格都是全局全序的。

为什么全序的成员资格视图有用?

  • 这允许将新的silo连接到集群中。这样,当一个新的silo加入集群时,它可以验证已经启动的每个其他silo的双向连接。如果某些已加入的silo没有回答(表明新silo的网络连接可能存在问题),则不允许新的silo加入。这确保了至少当一个silo启动时,集群中的所有silo之间是完全连接的(这一点已实现)。

  • silo中更高级别的协议,例如分布式的grain目录,可以利用成员资格视图有序的事实,并使用此信息,执行更智能的重复激活解决方案。特别是,当目录发现当成员资格因不断变化而创建了2个激活体时,它可能会决定停用基于现在过时的成员资格信息创建的旧激活(目前尚未实现这一点)。

扩展的成员资格协议:

  1. 为了实现此功能,我们利用了由MembershipTable提供的多行事务的支持。

  2. 我们在跟踪表更改的表中,添加了一个成员资格版本的行。

  3. 当silo S想要为silo P写入怀疑或死亡的声明:

    3.1 S读取最新的表内容。如果P已经死了,什么都不做。否则,

    3.2在同一事务中,将更改写入P的行,增加版本号并将其写回表中。

    3.3两种写入都以eTags为条件。

    3.4如果由于P的行或版本行上的eTag不匹配而导致事务中止,请再次尝试。

  4. 对表的所有写入都会修改并增加版本行。这样,对表的所有写入都被序列化(通过序列化对版本行的更改),并且由于silos只增加版本号,所以写入操作也按递增顺序,是完全有序的。

扩展的成员资格协议的可伸缩性:

在协议的扩展版本中,所有的写入都通过一行来序列化。这可能会损害集群管理协议的可伸缩性,因为它会增加并发表写入之间冲突的风险。为了部分缓解此问题,silo通过使用指数退避算法,来重试对表的所有写入操作。我们已观察到,扩展协议可以在Azure的生产环境中,最多可以容纳200个silo,可以平稳运行。但是,我们认为协议在扩展时超过1000个silo可能存在问题。在如此大的安装中,可以轻松禁用版本行的更新,本质上维护了集群管理协议的其余部分,并放弃全序的属性。另请注意,我们在这里指的是集群管理协议的可扩展性,而不是Orleans的其他协议。我们相信,Orleans运行时的其他部分(消息传递、分布式目录、grain承载、客户机到网关的连接)可扩展到数百个silo之外。

成员资格表:

如前所述,MembershipTable用作一个集合点,以便silo找到彼此和Orleans客户端找到silo,并且还有助于协调在成员资格视图上达成一致。我们目前有6个MembershipTable的实现:基于Azure表、SQL服务器、Apache ZooKeeper、Consul IO、AWS DynamoDB和用于开发的内存仿真。MembershipTable接口的定义在IMembershipTable中。

  1. Azure表存储 - 在此实现中,我们使用Azure部署ID作为分区键,并将silo 标识(ip:port:epoch)用作行键。它们共同保证了每个silo的唯一键。对于并发控制,我们使用基于Azure Table ETag的乐观并发控制。每次我们从表中读取时,我们都会为每个读取行存储eTag,并在我们尝试写回时使用该标签。每次写入时,Azure Table服务都会自动分配和检查eTags。对于多行事务,我们利用了Azure表提供的批处理事务支持,这保证了具有相同分区键的行上的序列化事务。

  2. SQL Server - 在此实现中,配置的部署ID用于区分部署以及哪些silo属于哪些部署。silo标识被定义为deploymentID、ip、port、epoch适当的表和列的组合。关系型后端使用乐观并发控制和事务,类似于在Azure Table实现上使用ETag的过程。关系型实现,期望数据库引擎生成使用的ETag。对于SQL Server,在SQL Server 2000上生成的ETag,是从对NEWID()的调用中获取的ETag 。在SQL Server 2005及更高版本上使用ROWVERSION。Orleans以不透明的VARBINARY(16)标识,来读取和写入关系型ETag,,并将它们以base64编码字符串,存储在内存中。Orleans支持使用UNION ALL(适用于Oracle,包括DUAL)进行多行插入,目前用于插入统计数据。可以在CreateOrleansTables_SqlServer.sql中看到SQL Server的准确实现和基本原理。

  3. Apache ZooKeeper - 在此实现中,我们使用配置的部署ID作为根节点,并使用silo 标识(ip:port@epoch)作为其子节点。它们共同保证了每个silo的唯一路径。对于并发控制,我们使用基于节点版本的乐观并发控制。每次从部署根节点读取时,我们都会为每个已读的子silo节点,存储其版本,并在我们尝试写回时使用该版本。每次节点的数据发生更改时,版本号都会由ZooKeeper服务原子地增加。对于多行事务,我们使用multi方法,该方法保证了具有相同父部署ID节点的silo节点上的序列化事务。

  4. Consul IO - 我们使用Consul的Key/Value存储来实现memberhop表。有关更多详细信息,请参阅Consul-Deployment。

  5. AWS DynamoDB - 在此实现中,我们使用集群部署ID作为分区键,并使用Silo 标识(ip-port-generation)作为RangeKey,使记录唯一。ETag属性通过在DynamoDB上进行条件写入,来实现乐观并发。实现逻辑与Azure Table存储非常相似。我们只实现了基本的成员协议(而不是扩展协议)。

  6. 用于开发设置的内存仿真。我们使用一个称为MembershipTableGrain的、特殊的系统grain,这种grain存在于一个指定的主要silo上,它仅用于开发设置。在任何实际生产用途中,不需要主silo。

配置:

成员资格协议,是通过OrleansConfiguration.xml文件中GlobalsLiveness元素配置的。默认值是根据Azure中的生产使用,进行了调优的,我们认为它们代表了良好的默认设置。一般来说,没有必要改变它们。

示例配置元素:

<Liveness ProbeTimeout = "5s" TableRefreshTimeout ="10s  DeathVoteExpirationTimeout ="80s" NumMissedProbesLimit = "3" NumProbedSilos="3" NumVotesForDeathDeclaration="2" />

实现了4种类型的liveness。liveness协议的类型,是通过OrleansConfiguration.xml文件中Globals节的SystemStore元素的SystemStoreType属性配置的。

  1. MembershipTableGrain - 成员资格表存储在主silo上的一个grain。这只是一个开发设置

  2. AzureTable - 成员资格表存储在Azure表中。

  3. SqlServer - 成员资格表存储在关系型数据库中。

  4. ZooKeeper- 成员资格表存储在ZooKeeper ensemble。

  5. Consul- 配置为自定义系统存储MembershipTableAssembly = "OrleansConsulUtils"。有关更多详细信息,请参阅Consul-Deployment。

  6. DynamoDB- 配置为自定义系统存储MembershipTableAssembly = "OrleansAWSUtils"

对于所有的liveness类型,常见配置变量,定义在Globals.Liveness元素中:

  1. ProbeTimeout - 探测其他silo存活性的时间或silo发送“我还活着”心跳信息的秒数。默认值为10秒。

  2. TableRefreshTimeout - 从成员资格表中获取更新的秒数。默认值为60秒。

  3. DeathVoteExpirationTimeout - 成员资格表中死亡投票的到期时间(秒)。默认值为120秒

  4. NumMissedProbesLimit - 来自某个silo的“我还活着”心跳消息的丢失的数量,或者是对怀疑这个silo已经死亡的探测、但未回复的数量。默认值为3。

  5. NumProbedSilos - 每个silo探测存活性的silo数量。默认值为3。

  6. NumVotesForDeathDeclaration - 将某个silo声明为死亡所需的非过期投票数(最多应为NumMissedProbesLimit)。默认值为2。

  7. UseLivenessGossip - 是否使用gossip优化,来加速传播活跃度信息。默认为true。

  8. IAmAliveTablePublishTimeout - 在成员资格表中定期写入此silo存活的秒数。仅用于诊断。默认为5分钟。

  9. NumMissedTableIAmAliveLimit - 来自某silo的“我还活着”消息的丢失数的更新,该更新会导致记录警告。不影响Liveness协议。默认值为2。

  10. MaxJoinAttemptTime - 在放弃之前,尝试加入silo集群的秒数。默认为5分钟。

  11. ExpectedClusterSize - 集群的预期大小。不需要非常准确,可以高估。用于调整重试的指数退避算法,来写入Azure表。默认值为20。

设计理由:

可能被问到的一个的问题是,为什么不完全依赖Apache ZooKeeper用于集群成员资格的实现,通过潜在地使用它对瞬息节点的组成员资格的开箱即用支持?为什么我们花费精力来实现自己的成员资格协议?主要有三个原因:

1)在云中部署/承载 - Zookeeper不是承载服务(至少在2015年7月撰写本文时,以及当我们在2011年夏天首次实现此协议时,没有任何版本的Zookeeper,由任何主要的云提供商,作为承载服务运行)。这意味着在云环境中,Orleans客户必须部署/运行/管理他们自己的ZK集群实例。这只是另一个不必要的负担,我们不想强迫我们的客户。通过使用Azure Table,我们依靠一个承载的、托管服务,使我们的客户,生活更加简单。基本上,在云中,使用云作为平台而不是基础设施。另一方面,当在本地运行并管理自己的服务器时,依赖ZK作为MembershipTable的实现,是一个可行的选择。

2)直接故障检测 - 当使用ZK的带瞬息节点的组成员资格时,将在Orleans服务器(ZK客户端)和ZK服务器之间执行故障检测。这可能不一定与Orleans服务器之间的实际网络问题相关。我们的愿望是,故障检测将准确地反映通信的集群内状态。具体来说,在我们的设计中,如果Orleans silo不能与MembershipTable通信,它不会被认为已经死亡,并且可以继续工作。与此相反,如果我们使用ZK的带瞬息节点的组成员资格,那么与ZK服务器断开连接,可能会导致Orleans silo(ZK客户端)被宣告死亡,而实际上它可能是活动的,并且完全可以正常工作。

3)可移植性和灵活性 - 作为Orleans理念的一部分,我们不希望强烈依赖任何特定技术,而是采用灵活的设计,可以通过不同的实现轻松切换不同的组件。这正是MembershipTable抽象所起的作用。

致谢:

我们要感谢Alex Kogan对该协议第一版的设计和实现所作的贡献。这项工作是作为2011年夏季微软研究院暑期实习的一部分完成的。基于MembershipTable的ZooKeeper实现,由Shay Hazor完成,SQL MembershipTable的实现由Veikko Eeva完成,AWS DynamoDB的MembershipTable实现由Gutemberg Ribeiro完成,基于MembershipTable的Consul的实现由Paul North完成。

你可能感兴趣的:(Orleans)