A-A+

苹果发布新的 Swift Server 框架:Swift Cluster Membership,这次的目标是集群

博客主机

很高兴为大家带来一个 Swift Server 生态系统新的开源项目 Swift Cluster Membership。这个库旨在促进 Swift 在服务端领域的发展:集群化多节点分布式系统。在这个库中,我们提供了可重用的,与运行时无关的成员协议实现,可以在各种集群中使用。

背景

集群成员协议是分布式系统的关键构建块(例如计算密集型集群,调度程序,数据库,键值存储等)。发布这个软件包,我们的目标是使此类系统的构建更加简单,因为它们不再需要依赖外部服务来为其处理服务成员资格。我们还希望邀请社区来合作,并开发其他更多的协议。

成员协议的核心是需要回答“谁是我的同伴?”这个问题。在一个有延迟或丢失消息,网络分区以及有无响应但仍然“活跃”的节点的分布式系统中,这个看似简单的任务实际上并不简单。为该问题提供一个可预测的、可靠的答案是集群成员协议的主要任务。

实现成员协议可以采取多种策略,同时这也是一个可以不断研究和完善的有趣的领域。因此,集群成员这个 package 并不专注于单个实现,而是提供一个各种分布式算法协作的空间。

随着初始版本的发布,我们也开源了这类成员协议的一个实现:SWIM。

使用 Swift 实现的 SWIMming

我们开源的第一个成员协议实现是 Scalable Weakly-consistent Infection-style process group Membership[1] 协议(简称 SWIM),以及 Lifeguard: Local Health Awareness for More Accurate Failure Detection[2] 这篇 2018 年的论文中提及的一些重要的协议扩展。

SWIM 是一个 gossip 协议[3],其中对等节点定期交换自己对其它节点状态的观察结果相关的信息,最终将信息传播给集群中所有其它成员。这类分布式算法可以很好地防御信息的丢失、网络分区和其它类似问题。

在上层,SWIM 的工作方式如下:

  • 成员定期去 ping 一个随机选择的可预知的对等节点。它通过向对等节点发送 .ping 消息来完成该任务,并期望返回一个 .ack。可以在下图中看到节点 A 如何去探测节点 B。
    • 交换的信息同时会携带一个 gossip 负载,该负载是信息发送方可探测的其它对等节点的部分信息,以及他们的成员状态(.alive, .suspect 等等)
  • 如果节点收到 .ack,则目标对等节点被视为仍处于活动状态(.alive)。否则,目标对等节点可能已终止/崩溃或由于其它原因而没有反馈。
    • 为了再次检查对等节点是否真的宕机,节点会通过向其它对等节点发送 .pingRequest 消息,以询问无响应对等节点的状态,然后向该对等节点直接发出 .ping 消息。
  • 如果这些 ping 操作都失败了,则节点将收到一个 .nack (negative acknowledgement) 消息,并且由于缺少 .ack 而导致探测的对等节点被标记为 .suspect。
image.png

上述机制不仅用作故障检测机制,而且还用作 gossip 机制,该机制承载有关集群的已知成员的信息。这样,成员即使没有所有成员的信息,也可以最终了解其对等节点的状态。值得指出的是,这种成员资格视角是弱一致性的,这意味着不能保证所有成员是在给定的时间点对其它的成员的观察是否是相同的。不过,这是构建更高层工具和系统的坚实基础。

一旦故障检测机制检测到无响应的节点,该节点最终将被标记为.dead,导致其不可撤消地从集群中删除。我们的实现提供了一个可选的扩展,并添加了一个 .unreachable 状态,但是大多数用户会发现它不是必须的,并且默认情况下是禁用的。有关合法状态转换的详细信息和规则,可以参考 SWIM.Status[4] 或者下图:

image.png

Swift Cluster Membership 实现协议的方式是通过提供协议的 Instance。例如,SWIM 实现被封装在与运行时无关的 SWIM.Instance 中,需要通过联网运行时与实例本身之间的某种粘合代码来“驱动”或“解释”。我们将这些实现的粘合代码称为“外壳”,并且该库有一个使用 SwiftNIO 的 DatagramChannel 实现的 SWIMNIOShell,DatagramChannel 通过 UDP 异步执行所有消息传递。替代实现可以使用完全不同的传输方式,也可以在其它现有的 gossip 系统上搭载 SWIM 消息。

SWIM实例还内置了对发出指标(使用swift-metrics)的支持,并且可以配置为通过传递 swift-log Logger 来记录内部详细信息。

示例:重用 SWIM 协议逻辑实现

这个库的主要目的是在需要某种形式的进程内成员资格服务的各种实现之间共享 SWIM.Instance 实现。该项目的 README[5] 文件中详细记录了实现自定义运行时的信息,因此,如果您有兴趣通过某种不同的传输方式实现 SWIM,请去那里看看。

实现新的传输可以归结为 "fill in the blanks" 练习:

首先,必须使用目标传输来实现对等协议:

public protocol SWIMPeer: SWIMAddressablePeer {    func ping(        payload: SWIM.GossipPayload,        from origin: SWIMAddressablePeer,        timeout: DispatchTimeInterval,        sequenceNumber: SWIM.SequenceNumber,        onComplete: @escaping (Result<SWIM.PingResponse, Error>) -> Void    )    // ...}

这通常意味着封装一些连接,通道或其他身份,并具有发送消息和在恰当时机调用适当的回调的能力。

然后,在对等方的接收端,节点必须实现以旧换新这些消息,并调用在 SWIM.Instance 中定义的所有相应的 on 回调。这些调用在内部执行所有 SWIM 协议指定的任务,并返回指令,这些指令很容易将 commands 解释为关于其应如何响应消息的实现。例如,在接收到 PingRequest 消息后,返回的指令可以将 Shell 指引到 .sendPing(target:pingRequestOrigin:timeout:sequenceNumber),这可以在目标节点上调用这个 ping 来简单地实现。

示例:使用 SwiftNIO 的 SWIMming

这个库包含一个端到端的示例[6],以及一个名为 SWIMNIOExample 的示例实现,该示例实现利用 SWIM.Instance 来启用一个简单的基于 UDP 的对等监视系统。这允许对等方使用 SWIM 协议通过发送 SwiftNIO 驱动的数据报来通信并互相通知节点故障。

SWIMNIOExample 实现仅作为示例提供,尚未考虑生产的用途,但是,只要付出一些努力,它肯定可以在某些用例中很好地实现。如果您有兴趣了解有关集群成员算法,可伸缩性基准测试和使用 SwiftNIO 本身的更多信息,那么这是一个很好的入门模块,也许一旦该模块足够成熟,我们可以考虑将其作为一个示例,基于 SwiftNIO 的集群应用程序的可重用组件。

以最简单的形式,将提供的 SWIM 实例和 SwiftNIO shell 结合起来构建一台简单的服务器,可以将提供的处理程序如下代码所示嵌入典型的 SwiftNIO 通道管道中:

let bootstrap = DatagramBootstrap(group: group)    .channelInitializer { channel in        channel.pipeline            // first install the SWIM handler, which contains the SWIMNIOShell:            .addHandler(SWIMNIOHandler(settings: settings)).flatMap {                // then install some user handler, it will receive SWIM events:                channel.pipeline.addHandler(SWIMNIOExampleHandler())        }    }bootstrap.bind(host: host, port: port)

然后,示例处理程序可以接收和处理 SWIM 集群成员资格更改事件:

final class SWIMNIOExampleHandler: ChannelInboundHandler {    public typealias InboundIn = SWIM.MemberStatusChangedEvent    let log = Logger(label: "SWIMNIOExampleHandler")    public func channelRead(context: ChannelHandlerContext, data: NIOAny) {        let change = self.unwrapInboundIn(data)        self.log.info(            """            Membership status changed: [\(change.member.node)]\             is now [\(change.status)]            """,            metadata: [                "swim/member": "\(change.member.node)",                "swim/member/status": "\(change.status)",            ]        )    }}

下一步

该项目目前处于预发布状态,我们想在发布稳定版本之前预留一些时间。我们主要关注 SWIM.Instance 的质量和正确性,但是在示例 Swift NIO 实现中还需要进行少量清理。一旦我们确认一些用例对 SWIM 实施的 API 充满信心,我们将发布 1.0 版本。

从那时起,我们希望继续研究其他成员实现,并最大程度地减少现有实现的开销。

给我留言

Copyright © ios教程,苹果粉丝,苹果资讯,ios入门教程,ios学习,ios程序员,ios视频教程,ios粉丝网 保留所有权利.   Theme  Ality

用户登录