3

I am working on a Google App Engine project (python/webapp2) where I am a little concerned with people abusing/spamming the service I am creating with a large number of requests. In an attempt to combat this potential, my idea is to limit the number of requests allowed per IP address in any given hour for certain parts of the applicaiton. My current plan is as follows:

On each request I will:

  1. grab the ip address from the header
  2. store this ip address in the dataStore with a time stamp
  3. delete any ip address entities in that are over an hour old
  4. count the number of dataStore entities with that IP address
  5. disallow access if there are more than given limit

My question is this:
Is this the best way to go about this? I am only a beginner here and I imagine that there is quite a bit of overhead of doing it this way and that possibly this is a common task that might have a better solution. Is there any better way to do this that is less resource intensive?

4

2 回答 2

11

过去,我使用 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(429)
    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 重新归零计数。(即它不是一个滑动窗口;它在每个时间段重置为零。)

于 2013-01-17T01:41:09.733 回答
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 的列表”) ,并且计时器可能会经常触发,以至于它可能不值得。

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

如果还不够好,我可能会先考虑是否可以在优化实现之前简化设计。例如,如果每个日历小时每个 IP 有 N 个连接,那么一切都会变得容易得多——只需为每个 IP 存储一个计数器(在 dataStore 或 memcache 中),并在每 XX:00 擦除所有计数器。这可以接受吗?

于 2013-01-17T01:54:57.993 回答