介绍 Swift 集群成员

我很高兴地宣布 Swift 服务器生态系统的一个新的开源项目:Swift Cluster Membership。该库旨在帮助 Swift 在服务器应用的新领域中发展:集群多节点分布式系统。借助此库,我们提供了可重用的、运行时无关的成员协议实现,这些实现可以在各种集群用例中采用。

背景

集群成员协议是分布式系统的关键构建块,例如计算密集型集群、调度程序、数据库、键值存储等等。随着此软件包的发布,我们旨在简化此类系统的构建,因为它们不再需要依赖外部服务来处理其服务成员资格。我们还想邀请社区协作开发和扩展更多的成员协议。

成员协议的核心需要为问题“谁是我的(活动的)对等节点?”提供答案。在分布式系统中,延迟或丢失的消息、网络分区以及无响应但仍然“活动”的节点是家常便饭,因此这项看似简单的任务实际上并非如此简单。集群成员协议的作用是为这个问题提供可预测、可靠的答案。

在实现成员协议时,可以进行各种权衡,并且这仍然是一个有趣的研究和持续改进领域。因此,集群成员软件包不打算专注于单一实现,而是作为该领域各种分布式算法的协作空间。

今天,随着此软件包的初始发布,我们开源了一个此类成员协议的实现:SWIM。

🏊🏾‍♀️🏊🏻‍♀️🏊🏾‍♂️🏊🏼‍♂️ 使用 Swift SWIM

我们开源的第一个成员协议是 可扩展的弱一致性感染式进程组 membership 协议(或 “SWIM”)的实现,以及 2018 年 Lifeguard: Local Health Awareness for More Accurate Failure Detection 论文中记录的一些值得注意的协议扩展。

SWIM 是一种流言协议,其中对等节点定期交换关于他们对其他节点状态的观察信息,最终将信息传播到集群中的所有其他成员。这类分布式算法对于任意消息丢失、网络分区和类似问题非常具有弹性。

在较高层面,SWIM 的工作方式如下

SWIM ping diagram

上述机制不仅充当故障检测机制,而且还充当 gossip 机制,该机制携带有关集群已知成员的信息。这样,即使没有预先列出所有成员,成员最终也会了解其对等节点的状态。但是,值得指出的是,此成员视图是弱一致性,这意味着无法保证(或知道,在没有其他信息的情况下)所有成员在任何给定时间点都对成员资格具有完全相同的视图。但是,它是更高级别工具和系统在其之上构建更强保证的出色构建块。

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

SWIM lifecycle diagram

Swift Cluster Membership 实现协议的方式是提供协议的“实例”。例如,SWIM 实现封装在运行时不可知的 SWIM.Instance 中,该实例需要通过网络运行时和实例本身之间的一些粘合代码来“驱动”或“解释”。我们将这些实现的粘合部分称为“Shells”,并且该库附带一个使用 SwiftNIODatagramChannel 实现的 SWIMNIOShell,该 Shell 通过 UDP 异步执行所有消息传递。替代实现可以使用完全不同的传输,或者在一些其他现有的 gossip 系统上搭载 SWIM 消息等。

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

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

此库的主要目的是在各种需要某种形式的进程内成员资格服务的实现之间共享 SWIM.Instance 实现。项目的 README 中深入记录了自定义运行时的实现,因此如果您有兴趣通过一些不同的传输实现 SWIM,请查看那里。

实现新的传输可以归结为“填空”练习

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

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<SomeMessage> 回调(在 SWIMProtocol 下分组)。这些调用在内部执行所有 SWIM 协议特定任务,并返回指令,这些指令是易于解释的“命令”,用于说明实现应如何对消息做出反应。例如,在接收到 PingRequest 消息后,返回的指令可以指示 shell .sendPing(target:pingRequestOrigin:timeout:sequenceNumber),这可以通过在 target 对等节点上调用此 ping 来简单地实现。

示例:使用 SwiftNIO SWIM

该存储库包含一个 端到端示例 和一个名为 SWIMNIOExample 的示例实现,该示例实现利用 SWIM.Instance 来启用基于 UDP 的简单对等节点监控系统。这允许对等节点通过发送由 SwiftNIO 驱动的数据报,使用 SWIM 协议进行 gossip 并相互通知节点故障。

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 版本。

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

其他资源

更多文档和示例可以在 GitHub 上找到。

参与其中

如果您对集群成员协议感兴趣,请参与进来!Swift Cluster Membership 是一个完全开源的项目,在 GitHub 上开发。随时欢迎开源社区的贡献。我们鼓励在 Swift 论坛上进行讨论。对于错误报告、功能请求和拉取请求,请使用 GitHub 存储库。

我们非常高兴看到您使用此库完成的惊人事情!