我的许多视图都会获取外部资源.我想确保在重载下我不会炸毁远程站点(和/或被禁止).
我只有一个履带,所以有一个中央锁可以正常工作.
所以细节:我想每秒最多允许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.但是,捕获约束异常并不那么强大.