Swift 服务发现

Swift 的服务发现 API。

服务发现是指服务如何在分布式系统中找到彼此。 此 API 库旨在建立一个标准,该标准可以由各种服务发现后端实现,例如基于 DNS 的后端、Zookeeper 等键值存储。换句话说,此库仅定义 API,类似于 SwiftLogSwiftMetrics;实际功能由后端实现提供。

这是一个社区驱动的开源项目的开始,积极寻求贡献,无论是代码、文档还是想法。除了为 SwiftServiceDiscovery 本身做出贡献之外,我们还需要与 SwiftServiceDiscovery 兼容的库,这些库管理服务注册和位置信息以进行查询。 API 文档中介绍了 SwiftServiceDiscovery 目前提供的功能,但它将随着社区的投入而不断发展。

入门

如果您有一个服务器端 Swift 应用程序,并且想在同一系统中找到其他服务以发出 HTTP 请求或 RPC,那么 SwiftServiceDiscovery 正是适合您的库。 您将在下面找到入门所需的一切。

概念

选择服务发现后端实现(仅限应用程序)

注意:如果您正在构建一个库,则无需关心本节。您的库的最终用户(应用程序)将决定使用哪个服务发现后端。 库不应更改服务发现实现,因为这是应用程序拥有的。

SwiftServiceDiscovery 仅提供服务发现 API。 作为应用程序所有者,您需要选择一个服务发现后端才能进行查询。

选择后端的方法是添加对所需后端实现的依赖项,并在程序开始时实例化它。

例如,假设您已选择假设的 DNSBasedServiceDiscovery 作为后端

// 1) Import the service discovery backend package
import DNSBasedServiceDiscovery

// 2) Create a concrete ServiceDiscovery object
let serviceDiscovery = DNSBasedServiceDiscovery()

由于 API 刚刚启动,因此还没有很多实现。 如果您有兴趣实现一个,请参阅下面的“实现服务发现后端”部分,其中解释了如何进行操作。 与 SwiftServiceDiscovery API 兼容的现有库列表

获取服务的实例

要获取当前实例列表(其中 resultResult<[Instance], Error>

serviceDiscovery.lookup(service) { result in
    ...
}

要获取当前实例列表(其中 resultResult<[Instance], Error>并且订阅未来的更改

let cancellationToken = serviceDiscovery.subscribe(
    to: service, 
    onNext: { result in
        // This closure gets invoked once at the beginning and subsequently each time a change occurs
        ...
    },
    onComplete: { reason in
        // This closure gets invoked when the subscription completes
        ...
    }
)

...

// Cancel the `subscribe` request
cancellationToken.cancel()

subscribe 返回一个 CancellationToken,您可以使用它来稍后取消订阅。 onComplete 是一个闭包,当订阅结束时(例如,当服务发现实例关闭时)或通过 CancellationToken 取消时,会调用该闭包。 CompletionReason 可用于区分导致完成的原因。

Async APIs

Async APIs 可用于 Swift 5.5 及更高版本。

要获取当前实例列表

let instances: [Instance] = try await serviceDiscovery.lookup(service)

要获取当前实例列表并且订阅未来的更改

for try await instances in serviceDiscovery.subscribe(to: service) {
    // do something with this snapshot of instances
}

async subscribe API 的底层是一个 AsyncSequence。 要结束订阅,只需跳出 for 循环。

组合器

SwiftServiceDiscovery 包括用于常见要求的组合器,例如转换和过滤实例。 例如

// Only include instances running on port 8080
let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration)
    .filterInstance { [8080].contains($0.port) }

实现服务发现后端

注意:除非您需要实现自定义服务发现后端,否则本节中的所有内容可能都不相关,因此请随时跳过。

添加依赖项

要添加对 API 包的依赖项,您需要在 Package.swift 中声明它

.package(url: "https://github.com/apple/swift-service-discovery.git", from: "1.3.0"),

并在您的库目标中,将“ServiceDiscovery”添加到您的依赖项

.target(
    name: "MyServiceDiscovery", 
    dependencies: [
        .product(name: "ServiceDiscovery", package: "swift-service-discovery"),
    ]
),

要成为所有 SwiftServiceDiscovery 使用者都可以使用的兼容服务发现后端,您需要实现一个符合 SwiftServiceDiscovery 提供的 ServiceDiscovery 协议的类型。 它包括两个方法,lookupsubscribe

lookup

/// Performs a lookup for the given service's instances. The result will be sent to `callback`.
///
/// `defaultLookupTimeout` will be used to compute `deadline` in case one is not specified.
///
/// - Parameters:
///   - service: The service to lookup
///   - deadline: Lookup is considered to have timed out if it does not complete by this time
///   - callback: The closure to receive lookup result
func lookup(_ service: Service, deadline: DispatchTime?, callback: @escaping (Result<[Instance], Error>) -> Void)

lookup 获取给定服务的当前实例列表,并将其发送到 callback。 如果该服务未知(例如,需要注册但尚未为该服务完成注册),则结果应为 LookupError.unknownService 失败。

后端实现应强制执行操作完成的时间限制。 如果给出了 deadline,则应遵守它,否则应使用 defaultLookupTimeout 计算一个。

subscribe

/// Subscribes to receive a service's instances whenever they change.
///
/// The service's current list of instances will be sent to `nextResultHandler` when this method is first called. Subsequently,
/// `nextResultHandler` will only be invoked when the `service`'s instances change.
///
/// ### Threading
///
/// `nextResultHandler` and `completionHandler` may be invoked on arbitrary threads, as determined by implementation.
///
/// - Parameters:
///   - service: The service to subscribe to
///   - nextResultHandler: The closure to receive update result
///   - completionHandler: The closure to invoke when the subscription completes (e.g., when the `ServiceDiscovery` instance exits, etc.),
///                 including cancellation requested through `CancellationToken`.
///
/// -  Returns: A `CancellationToken` instance that can be used to cancel the subscription in the future.
func subscribe(to service: Service, onNext nextResultHandler: @escaping (Result<[Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken

subscribe 将服务实例“推送”到 nextResultHandler。 后端实现应调用 nextResultHandler

必须为每个 subscribe 请求创建一个新的 CancellationToken。 如果取消令牌的 isCancelledtrue,则订阅已被取消,并且后端实现应停止调用相应的 nextResultHandler

后端实现还必须通过 completionHandler 通知订阅何时因任何原因结束(例如,服务发现实例正在关闭或通过 CancellationToken 请求取消),以便订阅者可以根据需要提交另一个 subscribe 请求。


请随时通过 https://forums.swift.org/c/server 与我们联系。