限制来自任何给定IP地址的请求数

时间:2013-01-17 01:15:54

标签: python google-app-engine webapp2

我正在开发一个Google App Engine项目(python / webapp2),我有点担心滥用/垃圾邮件我正在创建的服务有大量请求。为了克服这种潜力,我的想法是限制应用程序某些部分在任何给定时间内每个IP地址允许的请求数。我目前的计划如下:

每次请求我都会:

  1. 从标题中获取IP地址
  2. 使用时间戳
  3. 将此IP地址存储在dataStore中
  4. 删除超过一小时的任何IP地址实体
  5. 计算具有该IP地址的dataStore实体的数量
  6. 如果超出限额,则禁止访问
  7. 我的问题是:
    这是解决这个问题的最好方法吗?我在这里只是一个初学者,我想这样做会有相当多的开销,这可能是一个可能有更好解决方案的常见任务。有没有更好的方法来做这个资源消耗较少的方法?

2 个答案:

答案 0 :(得分:10)

过去,我用memcache做了这个,速度要快得多,特别是因为你只关心近似限制(近似因为memcache可以被系统刷新,可能不会被所有实例共享等等)。 )。您甚至可以使用它来为您过期密钥。这样的事情(假定self是一个webapp2请求处理程序,并且您已经导入了GAE的memcache库):

memcache_key = 'request-count-' + self.request.remote_addr

count = memcache.get(memcache_key)

if count is not None and count > MAX_REQUESTS:
    logging.warning("Remote user has %d requests; rejecting." % (count))
    self.error(503)
    return

count = memcache.incr(memcache_key)
if count is None:
    # key didn't exist yet
    memcache.add(memcache_key, 1, time=WINDOW_IN_SECONDS)

这将创建一个密钥,在WINDOW_IN_SECONDS时间内关于MAX_REQUESTS后拒绝用户,将每个WINDOW_IN_SECONDS的计数重新归零。 (即它不是一个滑动窗口;每个时间段都会重置为零。)

答案 1 :(得分:2)

首先,您的设计有两点需要注意:

  • 通常很容易让某人获得新的IP地址 - 将您的iPhone从LTE切换到3G,然后退回,拔掉并重新插入您的DSL模型,选择新的开放代理等等。所以,如果你是我希望这可以防止故意滥用,而不仅仅是那些没有意识到他们做得太多的人,这没什么帮助。

  • IP地址通常由NAT或按顺序共享。如果这意味着一个人,那么每个IP每小时200个请求似乎是合理的 - 但是如果它意味着BigCorp区域办事处的所有7500名员工呢?

无论如何,您的解决方案将起作用,并且,根据您的流量模式,它可能是合理的,但也有一些替代方案。

例如,您可能希望保留共享黑名单,而不是检查每个连接。当连接进入时,立即接受或拒绝基于该黑名单,并启动“更新数据库”作业。您可以采取进一步的技巧来合并更新,而不是每N秒更新一次,等等。当然,这意味着您现在拥有所有连接都可读的共享数据,并且可以通过某些后台作业进行写入,这意味着您已经打开了通往竞争条件和僵局的大门以及Guido努力确保您很少需要面对GAE的所有有趣的事情。

您可以使用memcache而不是dataStore。但是,您需要仔细修改您的密钥,以便它们对于一个简单的键值存储有意义,因此到期可以满足您的需求。例如,您可以保留一个键入IP的值加上时间戳或随机数或每个连接的任何值,以及键入IP的连接列表值,以便您查找其他值。从缓存中删除的任何值都不再计算,如果连接列表值下降,则用户必须降至0.但这会增加很多复杂性。

如果您的用户数量很少,每个用户都会发出大量请求,您可以使用计时器来减少或重置或重新计算每个IP。但是,如果您希望每小时有超过几百个不同的IP,则需要手动合并所有这些定时器,并且可能也会合并这些作业(例如,“在17:55:39,减少此17个IP列表”) ,计时器可能会经常发射,这可能不值得。

就个人而言,我会先做最简单的实施,然后进行压力测试和性能测试,如果它足够好,就不用担心了。

如果它足够好,我可能会在考虑优化实现之前考虑是否可以简化设计。例如,如果每个 calendar-hour 每个IP是N个连接,那么一切都变得更加容易 - 只需存储每个IP的计数器(在dataStore或memcache中),并在每个XX擦除所有计数器:00。那可以接受吗?