RxSwift的速率限制

时间:2017-05-14 05:57:25

标签: swift reactive-programming rx-swift

我正在寻找一种在HTTP客户端中实现速率限制的智能方法。假设API的速率限制是任何资源上每秒5个请求。现在,实现看起来与此类似:

final class HTTPClient: HTTPClientProtocol {

    func getUser() -> Observable<User> {
        return Observable<User>.create { (observer) -> Disposable in
            ...
        }
    }

    func getProfile() -> Observable<Profile> {
        return Observable<Profile>.create { (observer) -> Disposable in
            ...
        }
    }

    func getMessages() -> Observable<Messages> {
        return Observable<Messages>.create { (observer) -> Disposable in
            ...
        }
    }

    func getFriends() -> Observable<Friends> {
        return Observable<Friends>.create { (observer) -> Disposable in
            ...
        }
    }

}

理想情况下,我希望在整个应用程序中根据需要使用这些方法,而不必担心速率限制。

回到每秒5个请求的示例:前五个请求可以立即执行。但之后的所有要求都要等待。因此,在1秒的窗口内,最多可以执行5个请求。所有其他请求都必须等待。

在RxSwift中有没有聪明的方法呢?

2 个答案:

答案 0 :(得分:1)

您需要自定义计划程序。

final class DelayScheduler: ImmediateSchedulerType {

    init(delay: TimeInterval, queue: DispatchQueue = .main) {
        self.queue = queue
        dispatchDelay = delay
    }

    func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        let cancel = SingleAssignmentDisposable()
        lastDispatch = max(lastDispatch + dispatchDelay, .now())
        queue.asyncAfter(deadline: lastDispatch) {
            guard cancel.isDisposed == false else { return }
            cancel.setDisposable(action(state))
        }
        return cancel
    }

    var lastDispatch: DispatchTime = .now()
    let queue: DispatchQueue
    let dispatchDelay: TimeInterval
}

然后通过让所有Observable订阅此调度程序来实现您的服务:

final class HTTPClient: HTTPClientProtocol {

    func getUser() -> Observable<User> {
        return Observable<User>.create { (observer) -> Disposable in
            ...
        }.subscribeOn(scheduler)
    }

    func getProfile() -> Observable<Profile> {
        return Observable<Profile>.create { (observer) -> Disposable in
            ...
        }.subscribeOn(scheduler)
    }

    func getMessages() -> Observable<Messages> {
        return Observable<Messages>.create { (observer) -> Disposable in
            ...
        }.subscribeOn(scheduler)
    }

    func getFriends() -> Observable<Friends> {
        return Observable<Friends>.create { (observer) -> Disposable in
            ...
        }.subscribeOn(scheduler)
    }

    let scheduler = DelayScheduler(delay: 0.5)
}

答案 1 :(得分:0)

Daniel T使用自定义调度程序非常棒,我发现它在实践中运行良好。这是他的代码版本,它实现了真正的滑动窗口速率限制:

final class RateLimitedScheduler: ImmediateSchedulerType {

    let period: TimeInterval
    let queue: DispatchQueue

    var dispatchHistory: [DispatchTime]
    var dhIndex = 0

    init(maxEvents: Int, period: TimeInterval, queue: DispatchQueue = .main) {
        self.period = period
        self.queue = queue
        let periodAgo = DispatchTime.now() - period
        dispatchHistory = Array(repeating: periodAgo, count: maxEvents)
    }

    func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        let cancel = SingleAssignmentDisposable()
        queue.asyncAfter(deadline: nextDeadline()) {
            guard cancel.isDisposed == false else { return }
            cancel.setDisposable(action(state))
        }
        return cancel
    }

    private func nextDeadline() -> DispatchTime {
        let windowStartTime = dispatchHistory[dhIndex]
        let deadline = max(windowStartTime + period, DispatchTime.now())
        dispatchHistory[dhIndex] = deadline
        dhIndex = (dhIndex >= dispatchHistory.count - 1) ? 0 : (dhIndex + 1)
        return deadline
    }

}

请注意,完美的准确性要求跟踪前N个条目的发送时间,因此对于每个周期数百或数千个操作的速率,它的内存成本很高。考虑使用&#34;令牌桶&#34;对于那些情况 - 它不太准确但只需要恒定状态(见this thread)。