有效地使用速率受限的API(Echo Nest)和分布式客户端

时间:2012-08-28 06:56:01

标签: algorithm networking distributed-computing feedback throttling

背景

Echo Nest有一个rate limited API。给定的应用程序(在使用API​​密钥的请求中标识)每分钟最多可以进行120次REST调用。服务响应包括对最后一分钟呼叫总数的估计;重复滥用API(超出限制)可能导致API密钥被撤销。

当从单个机器(向客户端提供服务的Web服务器)使用时,很容易控制访问 - 服务器完全了解请求的历史,并且可以正确调节自身。

但我正在开发一个程序,其中分布式独立客户端并行发出请求。

在这种情况下,不太清楚最佳解决方案是什么。一般而言,问题似乎是不可判定的 - 如果超过120个客户,都没有历史记录,同时提出初始请求,那么将超过费率。

但由于这是一个个人项目,客户使用预计是零星的(突发性的),而且我的项目从未取得过巨大的成功,因此预计这不是一个大问题。更可能的问题是,有时候较少数量的客户希望尽快发出许多请求(例如,客户可能需要,特别是在第一次启动时发出数千个请求 - 这是可能的两个客户端大约会在同一时间启动,因此他们必须合作共享可用带宽。)

鉴于以上所有,适合客户的算法是什么,以便它们适当地限速?请注意,有限合作可能因为API返回总数所有客户端的最后一分钟请求。

当前解决方案

我目前的解决方案(当问题写完时 - 一个更好的方法作为答案)非常简单。每个客户端都记录了上次呼叫的时间以及API在该呼叫上报告的最后一分钟的呼叫数。

如果呼叫次数小于60(限制的一半),则客户端不会限制。这允许快速突发少量请求。

否则(即,当有更多先前的请求时),客户端计算它需要工作的限制率(即period = 60 / (120 - number of previous requests)),然后等待,直到前一个呼叫与当前时间之间的差距超过该时间段(以秒为单位;一分钟内60秒;每分钟最多120次请求)。这有效地限制了费率,如果它单独行动,它就不会超过限制。

但是上面有问题。如果仔细考虑,你会发现,对于大量的请求,单个客户端振荡并且没有达到最大吞吐量(这部分是因为“初始突发”会突然“落到窗外”,部分是因为算法没有充分利用其历史)。多个客户将在一定程度上合作,但我怀疑它是最优的。

更好的解决方案

我可以想象一个更好的解决方案,它使用客户端的完整本地历史记录,并使用隐藏马尔可夫模型模拟其他客户端。因此,每个客户端都会使用API​​报告来模拟其他(未知)客户端并相应地调整其速率。

我还可以设想一个单个客户端的算法,该算法逐步从小突发的无限行为过渡到许多请求的最佳有限行为,而不会引入振荡。

存在这样的方法吗?任何人都可以提供实施或参考吗?谁能想到更好的启发式方法呢?

我想这是某个已知的问题。在什么领域?排队理论?

我也猜测(参见前面的评论)没有最佳解决方案,并且可能存在一些在实践中运作良好的传说/传统/接受的启发式。我很想知道...目前我正努力在已知的网络协议中找出类似的问题(我想如果有的话,Perlman会有一些很好的解决方案)。

在一个需要中央服务器来协助合作的解决方案中,我也感兴趣(在较小程度上,如果程序变得流行,将来参考)。

声明

这个问题根本不是对Echo Nest的批评;他们的服务和使用条件都很棒。但是我越想到如何最好地使用它,它就变得越复杂/有趣......

此外,每个客户端都有一个本地缓存,用于避免重复呼叫。

更新

Possibly relevant paper

3 个答案:

答案 0 :(得分:3)

以上工作,但非常嘈杂,代码很乱。我现在使用更简单的方法:

  • 拨打电话
  • 从回复中,注意限制和计数
  • 计算

    barrier = now() + 60 / max(1, (limit - count))**greedy
    
  • 在下一个电话中,等到barrier

这个想法很简单:你应该等待一段时间,与那一分钟内剩下的请求数量成正比。例如,如果count为39且limit为40,则等待整整一分钟。但如果count为零,那么您很快就可以提出请求。 greedy参数是一种权衡 - 当大于1时,“第一次”呼叫的速度会更快,但您更有可能达到极限并最终等待60秒。

这与上面的方法类似,而且很多更强大。当客户“突发”时特别好,因为上面的方法在尝试估算线性费率时会感到困惑,而这很可能会让客户在需求低时“窃取”一些快速请求。

Code here

答案 1 :(得分:1)

经过一些实验,似乎最重要的是对当前连接速率的上限进行尽可能好的估计。

每个客户端都可以使用时间戳队列跟踪自己的(本地)连接速率。在每个连接上将时间戳添加到队列中,并丢弃超过一分钟的时间戳。然后从第一个和最后一个时间戳和条目数(减1)中找到“长期”(超过一分钟)的平均速率。可以从最后两个请求的时间找到“短期”(瞬时)速率。上限是这两个值的最大值。

每个客户端还可以估算外部连接速率(来自其他客户端)。 “长期”速率可以从服务器报告的最后一分钟的“已使用”连接数中找到,由本地连接数(来自上述队列)校正。 “短期”费率可以从自上次请求以来的“使用”数量估计(对于本地连接减去1),由时间差缩放。同样,使用上限(这两个值的最大值)。

每个客户端计算这两个速率(本地和外部),然后将它们相加以估计与服务器的总连接速率的上限。此值与目标费率范围进行比较,目前的费率范围设定为最大值的80%到90%(每分钟0.80.9 * 120)。

根据估算费率和目标费率之间的差异,每个客户修改自己的连接费率。这是通过获取先前的增量(最后一次连接和前一次连接之间的时间)并按1.1(如果速率超过目标)或0.9(如果速率低于目标)进行缩放来完成的。目标)。然后客户端拒绝建立新的连接,直到该缩放的增量已经过去(如果请求新的连接则通过休眠)。

最后,上述任何内容都不会强制所有客户端平均分配带宽。所以我再加上当地费率估算的10%。这具有优先高估率具有高费率的客户的速率的效果,这使得他们更有可能降低其费率。通过这种方式,“贪婪”的客户对减少消费的压力稍大,从长远来看,这似乎足以保持资源分配的平衡。

重要的见解是:

  • 通过采用“长期”和“短期”估算的最大值,当其他客户启动时,系统是保守的(并且更稳定)。

  • 没有客户端知道客户端的总数(除非是零或一个),但所有客户端都运行相同的代码,因此可以相互“信任”。

  • 鉴于上述情况,您无法对使用的速率进行“精确”计算,但您可以进行“常量”校正(在这种情况下,+ / - 10%因子),具体取决于全局率。

  • 对客户端连接频率的调整是针对最后两个连接之间的差值进行的(根据整个分钟的平均值进行调整太慢并导致振荡)。

  • 平衡消费可以通过轻微惩罚贪婪的客户来实现。

在(有限的)实验中,这种方法运行得相当好(即使是在多个客户端同时启动的最坏情况下)。主要缺点是:(1)它不允许初始“突发”(如果服务器具有很少的客户端并且客户端只有少量请求,则会提高吞吐量); (2)系统仍然会振动超过一分钟(见下文); (3)处理大量客户(在最坏的情况下,例如,如果它们都一次启动)需要更大的增益(例如20%校正而不是10%),这往往会使系统不稳定。

plot

(测试)服务器报告的“已用”量,与时间(Unix纪元)相对应。这是针对四个客户端(彩色),都试图消耗尽可能多的数据。

振荡来自通常的来源 - 校正滞后信号。它们通过(1)使用速率的上限(从瞬时值预测长期速率)和(2)使用目标频带来抑制。这就是为什么理解控制理论的人所得到的答案将会受到赞赏......

我不清楚分别估算本地和外部利率是很重要的(如果一方的短期利率很高而另一方的长期利率很高,它们可能会有所帮助),但我怀疑取消它会改善的东西。

总之:对于这种方法,这完全符合我的预期。它很有用,但因为它是一种简单的基于反馈的方法,所以它只能在有限的参数范围内保持稳定。我不知道可能有哪些替代方案。

答案 2 :(得分:0)

由于您使用的是Echonest API,为什么不利用每次API调用返回的速率限制标头?

一般来说,每分钟可获得120个请求。有三个标题可以帮助您自我调节API消耗:

X-Ratelimit-Used
X-Ratelimit-Remaining
X-Ratelimit-Limit

**(注意'Ratelimit'中的小写'ell' - 文档让你认为它应该大写,但实际上它是小写的。)

这些计数考虑了其他进程使用您的API密钥进行的调用。

非常整洁,对吧?好吧,我担心会有一些问题......

每分钟120请求真的是一个上限。你不能指望它。文档指出值可能会根据系统负载而波动。我已经看到它在我做过的一些调用中低至40,并且在某些情况下看到它低于零(我真的希望这是最老的API中的错误!)

您可以采取的一种方法是在利用率(使用除以限制)达到某个阈值后减慢速度。但请记住,在下一次通话中,您的限制可能已经下载得足够严重,“使用”大于“限制”。

这一直有效。由于Echonest没有在可预测的mannar中调整限制,因此在实践中很难避免400s。

以下是我发现有用的一些链接:

http://blog.echonest.com/post/15242456852/managing-your-api-rate-limit http://developer.echonest.com/docs/v4/#rate-limits