我正在寻找一种在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中有没有聪明的方法呢?
答案 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)。