将API调用限制为每分钟40次(Swift)



我每分钟对API的URL会话调用限制为40次。

我已经计时了60年代的通话次数,当接到40个电话时,我引入了睡眠(x(。其中,x是新分钟开始前的60秒。这很好用,在任何一分钟内电话都不会超过40。然而,仍然超过了限制,因为在一分钟结束时可能会有更多的电话,在下一个60秒计数开始时可能会更多。导致API错误。

我可以添加一个:

usleep(x)

其中x是60/40(以毫秒为单位(。然而,由于一些大型数据返回的时间比即时的简单查询要长得多。这将大大增加整个下载时间。

有没有一种方法可以跟踪实际速率,通过降低功能的速度来查看?

可能不是最整洁的方法,但它可以完美地工作。只需存储每次通话的时间,并进行比较,看看是否可以拨打新电话,如果不能,则需要延迟。

使用之前建议的60/40=1.5s(Minute/CallsPerMinute(的每个API调用之前的延迟方法,由于每个调用需要不同的时间来产生响应,因此进行500次调用所需的总时间为15分钟22秒。使用以下进场时间:11分钟52秒,因为没有引入不必要的延迟。

每次API请求前调用:

API.calls.addCall()

在执行新的API任务之前调用函数:

let limit = API.calls.isOverLimit()

if limit.isOver {
sleep(limit.waitTime)
}

后台支持代码:

var globalApiCalls: [Date] = []
public class API {
let limitePerMinute = 40 // Set API limit per minute
let margin = 2 // Margin in case you issue more than one request at a time
static let calls = API()
func addCall() {
globalApiCalls.append(Date())
}
func isOverLimit() -> (isOver: Bool, waitTime: UInt32)
{
let callInLast60s = globalApiCalls.filter({ $0 > date60sAgo() })

if callInLast60s.count > limitePerMinute - margin {
if let firstCallInSequence = callInLast60s.sorted(by: { $0 > $1 }).dropLast(2).last {
let seconds = Date().timeIntervalSince1970 - firstCallInSequence.timeIntervalSince1970
if seconds < 60 { return (true, UInt32(60 + margin) - UInt32(seconds.rounded(.up))) }
}
}
return (false, 0)
}
private func date60sAgo() -> Date
{
var dayComponent = DateComponents(); dayComponent.second = -60
return Calendar.current.date(byAdding: dayComponent, to: Date())!
}
}

不要使用sleep,而是使用计数器。您可以使用Semaphore(它是线程的计数器,一次允许x个线程(来完成此操作。

因此,如果你一次只允许40个线程,你将永远不会有更多的线程。新线程将被阻塞。这比调用sleep要高效得多,因为它将以交互方式处理长呼叫和短呼叫。

这里的技巧是每60秒调用一次这样的函数。这将使每分钟产生一个新的信号量,只允许40个调用。每个信号量不会相互影响,只会影响它自己的线程。

func uploadImages() {
let uploadQueue = DispatchQueue.global(qos: .userInitiated)
let uploadGroup = DispatchGroup()
let uploadSemaphore = DispatchSemaphore(value: 40)
uploadQueue.async(group: uploadGroup) { [weak self] in
guard let self = self else { return }
for (_, image) in images.enumerated() {
uploadGroup.enter()
uploadSemaphore.wait()
self.callAPIUploadImage(image: image) { (success, error) in
uploadGroup.leave()
uploadSemaphore.signal()
}
}
}
uploadGroup.notify(queue: .main) {
// completion
}
}

最新更新