如何在Ubuntu20.04上使用Redis实现PHP速率限制

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Apache Software Foundation 来接受捐赠。

介绍

Redis (Remote Dictionary Server ) 是一个内存中的开源软件。 它是一种使用服务器 RAM 的数据结构存储,其速度甚至比最快的固态驱动器 (SSD) 还要快数倍。 这使得 Redis 具有高度响应性,因此适用于 速率限制

速率限制是一种限制用户从服务器请求资源的次数的技术。 当用户可能试图在服务器上施加过多负载时,许多服务实施速率限制以防止滥用服务。

例如,当您使用 PHP 为您的 Web 应用程序实现公共 API(应用程序编程接口) 时,您需要某种形式的速率限制。 原因是当您向公众发布 API 时,您希望控制应用程序用户在特定时间范围内可以重复操作的次数。 如果没有任何控制,用户可能会使您的系统完全停止。

拒绝超过一定限制的用户请求,可以让你的应用程序顺利运行。 如果您有很多客户,速率限制会强制执行公平使用策略,允许每个客户高速访问您的应用程序。 速率限制也有利于降低带宽成本并最大限度地减少服务器上的拥塞。

通过在 MySQL 等数据库中记录用户活动来编写速率限制模块可能是实用的。 但是,当许多用户访问系统时,最终产品可能无法扩展,因为必须从磁盘获取数据并与设置的限制进行比较。 这不仅速度慢,而且关系数据库管理系统也不是为此目的而设计的。

由于 Redis 作为内存数据库工作,因此它是创建速率限制器的合格候选者,并且已被 证明可靠

在本教程中,您将在 Ubuntu 20.04 服务器上使用 Redis 实现用于速率限制的 PHP 脚本。

先决条件

在开始之前,您需要以下内容:

第 1 步 — 为 PHP 安装 Redis 库

首先,您将首先更新您的 Ubuntu 服务器软件包存储库索引。 然后,安装 php-redis 扩展。 这是一个允许您在 PHP 代码中实现 Redis 的库。 为此,请运行以下命令:

sudo apt update
sudo apt install -y php-redis

接下来,重新启动 Apache 服务器以加载 php-redis 库:

sudo systemctl restart apache2

更新软件信息索引并安装 PHP 的 Redis 库后,您现在将创建一个 PHP 资源,根据用户的 IP 地址限制用户的访问。

第 2 步 — 构建用于速率限制的 PHP Web 资源

在此步骤中,您将在 Web 服务器的根目录 (/var/www/html/) 中创建一个 test.php 文件。 该文件将可供公众访问,用户可以在 Web 浏览器中键入其地址来运行它。 但是,作为本指南的基础,您稍后将使用 curl 命令测试对资源的访问。

示例资源文件允许用户在 10 秒的时间范围内访问它 3 次。 试图超过限制的用户将收到错误消息,通知他们他们已受到速率限制。

该文件的核心功能在很大程度上依赖于 Redis 服务器。 当用户第一次请求资源时,文件中的 PHP 代码会根据用户的 IP 地址在 Redis 服务器上创建一个密钥。

当用户再次访问资源时,PHP 代码会尝试将用户的 IP 地址与存储在 Redis 服务器中的键匹配,如果键存在,则将值加一。 PHP 代码将继续检查增加的值是否达到设置的最大限制。

基于用户 IP 地址的 Redis 密钥将在 10 秒后过期; 在此时间段之后,将再次开始记录用户对 Web 资源的访问。

首先,打开 /var/www/html/test.php 文件:

sudo nano /var/www/html/test.php

接下来,输入以下信息来初始化 Redis 类。 请记住为 REDIS_PASSWORD 输入适当的值:

/var/www/html/test.php

<?php

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('REDIS_PASSWORD');

$redis->auth 实现对 Redis 服务器的明文认证。 当您在本地工作时(通过 localhost)可以这样做,但如果您使用的是远程 Redis 服务器,请考虑使用 SSL 身份验证

接下来,在同一个文件中,初始化以下变量:

/var/www/html/test.php

. . .
$max_calls_limit  = 3;
$time_period      = 10;
$total_user_calls = 0;

您已经定义:

  • $max_calls_limit:是用户可以访问资源的最大调用次数。
  • $time_period:根据 $max_calls_limit 定义允许用户访问资源的时间范围(以秒为单位)。
  • $total_user_calls:初始化一个变量,该变量检索用户在给定时间范围内请求访问资源的次数。

接下来,添加以下代码以检索请求 Web 资源的用户的 IP 地址:

/var/www/html/test.php

. . .
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $user_ip_address = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $user_ip_address = $_SERVER['REMOTE_ADDR'];
}

虽然此代码出于演示目的使用用户的 IP 地址,但如果您在服务器上拥有需要身份验证的受保护资源,则可以使用用户的用户名或访问令牌记录用户的活动。

在这种情况下,每个通过身份验证进入系统的用户都将具有唯一标识符(例如,客户 ID、开发人员 ID、供应商 ID,甚至是用户 ID)。 (如果您对此进行配置,请记住使用这些标识符代替 $user_ip_address。)

对于本指南,用户 IP 地址足以证明这一概念。 因此,一旦您在前面的代码片段中检索到用户的 IP 地址,请将下一个代码块添加到您的文件中:

/var/www/html/test.php

. . .
if (!$redis->exists($user_ip_address)) {
    $redis->set($user_ip_address, 1);
    $redis->expire($user_ip_address, $time_period);
    $total_user_calls = 1;
} else {
    $redis->INCR($user_ip_address);
    $total_user_calls = $redis->get($user_ip_address);
    if ($total_user_calls > $max_calls_limit) {
        echo "User " . $user_ip_address . " limit exceeded.";
        exit();
    }
}

echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";

在此代码中,您使用 if...else 语句检查 Redis 服务器上是否存在使用 IP 地址定义的密钥。 如果键不存在,if (!$redis->exists($user_ip_address)) {...},则使用代码 $redis->set($user_ip_address, 1); 设置它并将其值定义为 1

$redis->expire($user_ip_address, $time_period); 将密钥设置为在时间段内过期——在本例中为 10 秒。

如果用户的 IP 地址不作为 Redis 键存在,则将变量 $total_user_calls 设置为 1

...else {...}... 语句块中,您使用 $redis->INCR($user_ip_address); 命令将每个 IP 地址键的 Redis 键集的值增加 1。 仅当密钥已在 Redis 服务器中设置并计为重复请求时才会发生这种情况。

语句 $total_user_calls = $redis->get($user_ip_address); 通过检查用户在 Redis 服务器上基于 IP 地址的密钥来检索用户发出的请求总数。

在文件末尾,您使用 ...if ($total_user_calls > $max_calls_limit) {... }.. 语句检查是否超出限制; 如果是这样,你用 echo "User " . $user_ip_address . " limit exceeded."; 提醒用户。 最后,您将使用 echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds"; 语句通知用户他们在该时间段内进行的访问。

添加所有代码后,您的 /var/www/html/test.php 文件将如下所示:

/var/www/html/test.php

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('REDIS_PASSWORD');

$max_calls_limit  = 3;
$time_period      = 10;
$total_user_calls = 0;

if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $user_ip_address = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $user_ip_address = $_SERVER['REMOTE_ADDR'];
}

if (!$redis->exists($user_ip_address)) {
    $redis->set($user_ip_address, 1);
    $redis->expire($user_ip_address, $time_period);
    $total_user_calls = 1;
} else {
    $redis->INCR($user_ip_address);
    $total_user_calls = $redis->get($user_ip_address);
    if ($total_user_calls > $max_calls_limit) {
        echo "User " . $user_ip_address . " limit exceeded.";
        exit();
    }
}

echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";

编辑完 /var/www/html/test.php 文件后,保存并关闭它。

您现在已经编写了在 test.php Web 资源上对用户进行速率限制所需的逻辑。 在下一步中,您将测试您的脚本。

第 3 步 — 测试 Redis 速率限制

在此步骤中,您将使用 curl 命令来请求您在 Step 2 中编码的 Web 资源。 要全面检查脚本,您将在单个命令中请求资源五次。 可以通过在 test.php 文件末尾包含一个占位符 URL 参数来执行此操作。 在这里,您在请求结束时使用值 ?[1-5] 来执行 curl 命令五次。

运行以下命令:

 curl -H "Accept: text/plain" -H "Content-Type: text/plain" -X GET http://localhost/test.php?[1-5]

运行代码后,您将收到类似于以下内容的输出:

Output[1/5]: http://localhost/test.php?1 --> <stdout>
--_curl_--http://localhost/test.php?1
Welcome 127.0.0.1 total calls made 1 in 10 seconds
[2/5]: http://localhost/test.php?2 --> <stdout>
--_curl_--http://localhost/test.php?2
Welcome 127.0.0.1 total calls made 2 in 10 seconds
[3/5]: http://localhost/test.php?3 --> <stdout>
--_curl_--http://localhost/test.php?3
Welcome 127.0.0.1 total calls made 3 in 10 seconds
[4/5]: http://localhost/test.php?4 --> <stdout>
--_curl_--http://localhost/test.php?4
User 127.0.0.1 limit exceeded.
[5/5]: http://localhost/test.php?5 --> <stdout>
--_curl_--http://localhost/test.php?5
User 127.0.0.1 limit exceeded.

您会注意到,前三个请求运行没有问题。 但是,您的脚本限制了第四个和第五个请求的速率。 这证实了 Redis 服务器正在限制用户请求的速率。

在本指南中,您为以下两个变量设置了较低的值:

/var/www/html/test.php

...
$max_calls_limit  = 3;
$time_period      = 10;
...

在生产环境中设计应用程序时,您可以考虑更高的值,具体取决于您希望用户访问应用程序的频率。

最佳做法是在设置这些值之前检查实时统计信息。 例如,如果您的服务器日志显示平均用户每 60 秒点击您的应用程序 1,000 次,您可以使用这些值作为限制用户的基准。

为了更好地说明问题,以下是一些速率限制实现的真实示例(截至 2021 年):

结论

本教程在 Ubuntu 20.04 服务器上实现了一个 PHP 脚本,用于使用 Redis 进行速率限制,以防止您的 Web 应用程序无意或恶意过度使用。 您可以根据您的用例扩展代码以进一步满足您的需求。

您可能希望保护您的 Apache 服务器以供生产使用; 遵循 如何在 Ubuntu 20.04 上使用 Let's Encrypt 保护 Apache 教程。

您还可以考虑阅读 Redis 如何用作数据库缓存。 试试我们的 如何在 Ubuntu 20.04 教程中使用 PHP 将 Redis 设置为 MySQL 的缓存。

您可以在我们的 PHPRedis 主题页面上找到更多资源。