python – Django:简单的速率限制

我的许多视图都会获取外部资源.我想确保在重载下我不会炸毁远程站点(和/或被禁止).

我只有一个履带,所以有一个中央锁可以正常工作.

所以细节:我想每秒最多允许3次查询到主机,并且其余的块最多持续15秒.我怎么能(轻松)这样做?

一些想法:

>使用django缓存

>似乎只有1秒的分辨率

>使用基于文件的信号量

>易于锁定并发.不知道如何确保每秒只发生3次提取.

>使用一些共享内存状态

>我宁愿不安装更多的东西,但如果必须的话.

最佳答案 一种方法;创建一个这样的表:

class Queries(models.Model):
    site = models.CharField(max_length=200, db_index=True)
    start_time = models.DateTimeField(null = True)
    finished = models.BooleanField(default=False)

这记录了每个查询何时发生,或者如果限制阻止它立即发生,将在未来发生. start_time是动作开始的时间;如果该操作目前正在阻止,那么将来就是这样.

我们不是考虑每秒的查询数,而是考虑每个查询的秒数;在这种情况下,每个查询1/3秒.

每当要执行某个操作时,请执行以下操作:

>为操作创建一行. q = Queries.objects.create(site = sitename)
>在刚刚创建的对象(q.id)上,将start_time原子设置为此站点的最大start_time加上1/3秒.如果将来最大的是10秒,那么我们可以在10 1/3秒开始行动.如果那个时间过去了,请将它夹到现在().
>如果刚刚设置的start_time是将来,请睡到那个时间.如果将来太远(例如超过15秒),请删除行并输出错误.
>查询完成后,将finish设置为True,以便稍后可以清除该行.

原子动作是重要的.您不能简单地在查询上进行聚合,然后保存它,因为它会竞争.我不知道Django是否可以原生地执行此操作,但在原始SQL中它很容易:

UPDATE site_queries
SET start_time = MAX(now(), COALESCE(now(), (
    SELECT MAX(start_time) + 1.0/3 FROM site_queries WHERE site = site_name
)))
WHERE id = object_id

然后,重新加载模型并在必要时休眠.您还需要清除旧行.像Queries.objects.filter(site = site,finished = True).exclude(id = id).delete()之类的东西可能会起作用:删除除刚刚创建的查询之外的所有已完成的查询. (这样,您永远不会删除最新的查询,因为以后的查询需要安排它.)

最后,确保UPDATE不会在事务中发生.必须启用自动提交才能使其正常工作.否则,UPDATE将不是原子的:两个请求可能同时更新,并收到相同的结果. Django和Python通常会自动关闭,因此您需要将其打开然后再关闭.使用Postgres,这是connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)和ISOLATION_LEVEL_READ_COMMITTED.我不知道如何用MySQL做到这一点.

(我认为在Python的DB-API中关闭autocommit的默认设置是一个严重的设计缺陷.)

这种方法的好处是它非常简单,具有简单的状态;你不需要像事件监听器和唤醒这样的东西,它们有各自的问题.

可能的问题是,如果用户在延迟期间取消请求,无论您是否执行操作,仍会强制执行延迟.如果您从未启动该操作,则其他请求将不会向下移动到未使用的“时间段”.

如果您无法使autocommit工作,则解决方法是向(site,start_time)添加UNIQUE约束. (我不认为Django直接理解这一点,所以你需要自己添加约束.)然后,如果竞争发生并且同一站点的两个请求同时结束,其中一个将抛出约束您可以捕获的异常,您可以重试.您也可以使用普通的Django聚合而不是原始SQL.但是,捕获约束异常并不那么强大.

点赞