【问题标题】:A portable way of providing an IP-based cooldown period?提供基于 IP 的冷却时间的便携方式?
【发布时间】:2015-09-17 07:00:26
【问题描述】:

我有一个在网络服务器上运行的 PHP API 前端。这个特定的 PHP 程序是分发的,因此它应该尽可能地便携。

我要实现的功能是IP冷却期,这意味着同一个IP每秒最多只能请求API两次,这意味着至少有500ms的延迟。

我想到的方法是将 IP 与最新的请求时间戳一起存储在 MySQL 数据库中。我通过以下方式获得 IP:

if (getenv('REMOTE_ADDR'))
    $ipaddress = getenv('REMOTE_ADDR');

但是有些服务器可能没有 MySQL 数据库,或者安装它的用户没有访问权限。另一个问题是数据库的清理。

是否有更便携的方式来临时存储 IP(记住 IPv6)?

如何自动清理超过 500 毫秒的 IP,同时尽可能减少对性能的影响?

另外:我对查看存储的 IP 没有兴趣,这只是延迟。

【问题讨论】:

  • 看看here。它提供了一种非常方便的方式。
  • @Andrew 谢谢,这有助于创建我自己的基于文件的解决方案!

标签: php mysql ip-address


【解决方案1】:

这就是我现在使用文件解决它的方法。

程序

  1. 获取客户端 IP 并对其进行哈希处理(以防止文件读取)。
  2. 打开IP文件并扫描每一行
  3. 比较当前记录的时间和当前时间
  4. 如果差异大于设置超时转到 5.,否则 7.
  5. 如果 IP 匹配客户端,则创建更新记录,否则
  6. 删除记录。
  7. 如果 IP 匹配客户端,则提供失败消息,否则复制记录。

示例代码

<?php

$sIPHash    = md5($_SERVER[REMOTE_ADDR]);
$iSecDelay  = 10;
$sPath      = "bucket.cache";
$bReqAllow  = false;
$iWait      = -1;
$sContent   = "";

if ($nFileHandle = fopen($sPath, "c+")) {
    flock($nFileHandle, LOCK_EX);
    $iCurLine = 0;
    while (($sCurLine = fgets($nFileHandle, 4096)) !== FALSE) {
        $iCurLine++;
        $bIsIPRec = strpos($sCurLine, $sIPHash);
        $iLastReq = strtok($sCurLine, '|');
        // this record expired anyway:
        if ( (time() - $iLastReq) > $iSecDelay ) {
            // is it also our IP?
            if ($bIsIPRec !== FALSE) {
                $sContent .= time()."|".$sIPHash.PHP_EOL;
                $bReqAllow = true;
            }
        } else {
            if ($bIsIPRec !== FALSE) $iWait = ($iSecDelay-(time()-$iLastReq));
            $sContent .= $sCurLine.PHP_EOL;
        }
    }
}

if ($iWait == -1 && $bReqAllow == false) {
    // no record yet, create one
    $sContent .= time()."|".$sIPHash.PHP_EOL;
    echo "Request from new user successful!";
} elseif ($bReqAllow == true) {
    echo "Request from old user successful!";
} else {
    echo "Request failed! Wait " . $iWait . " seconds!";
}

ftruncate($nFileHandle, 0);
rewind($nFileHandle);
fwrite($nFileHandle, $sContent);
flock($nFileHandle, LOCK_UN);
fclose($nFileHandle);
?>

备注

新用户

如果 IP 哈希与任何记录都不匹配,则会创建一条新记录。注意:如果您没有权限,访问可能会失败。

内存

如果您预计流量很大,请一起切换到this 之类的数据库解决方案。

冗余代码

“但是 minxomat”,你可能会说,“现在每个客户端都循环遍历整个文件!”。是的,确实,这就是我想要它作为我的解决方案的方式。这样,每个客户端都负责清理整个文件。即便如此,性能影响仍然很低,因为如果每个客户端都在清理,文件大小将保持在绝对最小值。如果这种方式不适合您,请更改此设置。

【讨论】:

  • 你有竞争条件......两个 PHP 请求将尝试添加自己的 IP,但是当第一个正在清理时,第二个会将其 IP 写入文件中。然后 1st 将用其先前读取的版本(不包含第二个 IP)替换它。当你得到更多的请求/秒时,情况会更糟
  • @Marki555 真的有那么大的问题吗?更新/删除记录时会重新加载文件,因此冲突必须在那个小时间范围内发生,还是我遗漏了什么?
  • 取决于.. 如果您有 500 个请求/秒,那么 10 秒的 IP 将是 5000。比较长字符串哈希是低效的(与存储为 32 位数字的 IP 相比),例如可能需要 4 毫秒。这意味着每次您将有 2 个同时运行。
  • 如果您需要担心客户端每秒访问超过两次,相信我您也需要担心竞争条件。您的基本方法看起来是有效的,但可以使用代码,因此您只使用“标准”函数(即没有file_put_contents 等),并确保在您的fopen 调用之后立即使用flock($nFileHandle, LOCK_EX); 并在任何之前使用flock($nFileHandle, LOCK_UN); fclose 打电话。无论“清理”循环是否找到 IP(即fwrite 关闭句柄之前的新 IP),您还应该减少到只打开一次文件
  • @Marki555 对我来说仍然可以管理。一个成功的 API 请求可能需要长达 120 秒,因此在某些客户端正在清理时锁定文件应该不是问题,因为即使是 2 秒的延迟对我来说也可以忽略不计。
猜你喜欢
  • 1970-01-01
  • 2021-02-16
  • 1970-01-01
  • 2021-05-15
  • 2021-05-02
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多