Orleans的成员管理和故障检测故障检测
简介
Orleans框架是一个基于.NET平台的开源分布式系统框架,用于开发可扩展,高可用,高性能的云服务应用程序。它采用了Actor模型,将分布式系统中的各个节点抽象成为Actor,使得开发者可以更加专注于业务逻辑的实现,而无需关注底层的通信和协调机制。
Orleans框架中的故障检测算法主要是基于心跳检测实现的。通过选择一些节点来进行检测,从而实现故障检测和恢复。
算法详情
Orleans通过内置的成员管理协议提供集群管理,我们有时将其称为Silo Membership
(Silo
在Orleans中就是节点的意思)。该协议的目标是让所有Silo
就当前活跃Silo
集合达成一致,检测失败的Silo
,并允许新的Silo
加入集群。下文中提到的节点与Silo
的概念是一致的。
该协议依赖 IMembershipTable
的一组接口。IMembershipTable
可以理解是一组操作数据库的接口,用于存储当前的节点列表以及一些与故障检测相关的信息。目前,IMembershipTable有多种实现:基于 Azure 表存储、SQL Server、Apache ZooKeeper、Consul IO、AWS DynamoDB 和用于开发节点使用的内存模拟。
-
最开始时每个节点都调用
IMembershipTable
将自己的信息(ip:port:epoch
)写入到数据库中。epoch其实就是节点启动时间,因此ip:port:epoch
是全局唯一的。 -
节点间通过心跳来互相监控,心跳和业务消息走的是同一条TCP连接,所以心跳成功与否与服务器的实际运行状况相关。节点间监控并不是all-to-all的,而是从一致性HASH环上选择X个后继节点进行检测。比如,如果有10个节点,X=3,那么节点S就会在Hash环上选取S节点后面的三个节点进行检测。
-
如果节点S监控了P,且多次(N)未收到P的回复。则它会将带时间戳的怀疑信息(可理解为投票)写入
IMembershipTable
中。 -
如果K秒内有超过Z个节点怀疑P是故障的,则S会将P标记为故障,并写入
IMembershipTable
,并广播所有节点要求所有节点重新读取成员列表(每个节点本身也会定时读取的)。 -
更详细地来讲:
- 当S怀疑P时,它会写入"S在时间T1怀疑P"
- 仅有S怀疑P是没用的,需要在时间窗口T(通常为3分钟)内有Z个不同节点怀疑,才能宣布P的死亡
- 如果S是最后一个怀疑者(即在T时间内已经有Z-1个节点怀疑P),则S决定宣布P死亡,在这种情况下,S会将P死亡的信息写入
IMembershipTable
。- 注意此处是有并发控制的,同时只有一个节点会写入成功,多个节点并发写可能会导致某些写失败,写入失败时会重试直到成功。
- 每个节点也会定时读取成员列表进行更新,这样节点可以尽快地了解其他节点的加入或退出事件。
- Orleans的默认配置 心跳周期:10s、T:3分钟,Z:2,X:3,N:3。即:每个节点监控3个后继节点,也被3个前继节点监控。S每10秒发送一次心跳给P,连续3次未收到回复则怀疑P故障,当3分钟内有2个节点都怀疑P故障时,认为P真的故障了。
- 主动退出机制:如果某个节点与其他节点失去了通信,该节点进程本身还在运行,此节点还是会被其他节点认为故障的。当本节点发现自己被其他节点认为故障时,会主动退出。所以这也需要另外的机制来保障进程退出后被重新拉起,比如k8s、自行实现的看门狗等。
算法的特点
-
可以处理任意数量的失败
传统的比如基于Paxos的算法,故障节点不能超过一半。 而本算法没有此限制,微软官方文档说他们在生产环境上看到过一半以上的节点出现故障时,此系统仍然正常运行。
-
准确性和完整性可调节,可以通过调节参数来在两者之间进行取舍
-
规模
本算法可以处理上千、甚至上万台服务器。这与传统的基于 Paxos 的解决方案形成鲜明对比,例如组通信协议,众所周知,组通信协议的规模不能超过数十个。
-
诊断
诊断也非常的方便,因为算法的中间过程都写在
IMembershipTable
中,可以方便地看到故障检测情况。 -
为什么需要可靠的持久存储来实现
IMembershipTable
我们为 IMembershipTable 使用持久存储(Azure 表、SQL Server、AWS DynamoDB、Apache ZooKeeper 或 Consul IO KV)有两个目的。首先,它被用作节点相互寻找以及客户端寻找节点的集合点。其次,将成员资格视图存储在可靠的存储中,并使用该存储提供的并发控制机制来就谁还活着、谁已经死了达成一致。这样,从某种意义上说,我们的协议将分布式共识的难题外包给了存储服务。
-
如果存储无法访问时会如何
存储服务关闭、不可用或存在通信问题时------在这种情况下,我们的协议不会错误地宣布节点已死亡。已经存在的节点会继续运行。但是,我们将无法声明某个节点已故障(就算检测到某些节点已故障,也无法将此事实写入表中),并且也无法允许新的节点加入。因此,完整性会受到影响,但准确性不会。此外,在出现部分网络分区的情况下(例如某些节点可以访问表,而有些则不能),我们可能会将死筒仓声明为已死,但需要一些时间才能让所有其他筒仓了解它。因此检测可能会延迟,但我们绝不会因为表不可用而误杀某个节点。
-
各个节点还会定时(例如5分钟一次)将"我还活着"的信息写入表中,这个仅用于调试排查问题时使用,对算法本身没啥作用。
配置:
默认配置如下:
xml
<Liveness ProbeTimeout="5s"
TableRefreshTimeout="10s"
DeathVoteExpirationTimeout="80s"
NumMissedProbesLimit="3"
NumProbedSilos="3"
NumVotesForDeathDeclaration="2" />
这个默认值是根据Azure多年生产使用情况确定的,微软相信它们代表了良好的默认设置。一般来说没必要改变他们。
设计原理
人们可能会问的一个自然问题是,为什么不完全依赖 Apache ZooKeeper 来实现集群成员身份,为什么我们要费心实施我们的会员协议?主要有以下三个原因:
-
在云中部署/托管
Zookeeper 不是托管服务(至少在 2015 年 7 月撰写本文时,而且当我们在 2011 年夏天首次实现此协议时,任何主要云提供商都没有作为托管服务运行的 Zookeeper 版本)。这意味着在云环境中 Orleans 客户必须部署/运行/管理他们的 ZK 集群实例。这只是另一个不必要的负担,我们不想强加给我们的客户。通过使用 Azure Table,我们依赖于托管、托管服务,这使我们客户的生活变得更加简单。基本上,在云中,将云用作平台,而不是基础设施。另一方面,当在本地运行并管理服务器时,依赖 ZK 作为 IMembershipTable 的实现是一个可行的选择。
-
直接故障检测
使用ZK时,ZK会对Orleans节点进行健康检测,但是这并不能说明Orleans节点之间实际的通信状态。比如ZK和Orleans节点网络故障了,但实际上Orleans节点还是能正常提供服务的,此时ZK仍然会认为Orleans节点故障。而本文提出的算法中,即使Orleans各节点与存储服务之间通信故障,依然可以继续提供服务。
-
便携性和灵活性
作为 Orleans 理念的一部分,我们不想强迫对任何特定技术的强烈依赖,而是拥有灵活的设计,可以轻松地在不同的实现中切换不同的组件。这正是 IMembershipTable 抽象所服务的目的。
参考资料
[1] Orleans官方文档