我的许多观点都会获取外部资源。我想确保在重载下我不会炸毁远程站点(和/或被禁止)。
我只有1个爬虫,所以有一个中央锁可以正常工作。
所以细节:我希望每秒最多允许3个主机查询,并且其余的块最多15秒。我怎么能(轻松)这样做?
一些想法:
答案 0 :(得分:1)
使用不同的进程来处理抓取,以及它和Django之间的通信队列呢?
通过这种方式,您可以轻松更改并发请求的数量,并且还可以自动跟踪请求,而不会阻止调用者。
最重要的是,我认为这有助于降低主应用程序的复杂性(在Django中)。
答案 1 :(得分:1)
一种方法;创建一个这样的表:
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秒。
每当要执行某项操作时,请执行以下操作:
start_time
原子设置为此站点的最大start_time加上1/3秒。如果将来最大的是10秒,那么我们可以在10 1/3秒开始行动。如果那个时间过去了,请把它夹到现在()。原子动作是重要的。您不能简单地在查询上进行聚合,然后保存它,因为它会竞争。我不知道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。但是,捕捉约束异常并不那么强大。