Redis,也称为 Remote Dictionary Server,是一个开源的内存数据库。它是一个运行在服务器 RAM 上的数据结构存储系统,这比最快的固态硬盘(SSD)还要快得多。因此,Redis 的响应速度非常快,非常适合用于速率限制。
速率限制 限制了用户向服务器请求资源的次数。许多服务使用速率限制来防止服务滥用,例如用户试图用过多的负载压垮服务器。例如,当使用 PHP 来为您的 Web 应用程序开发公共 API(应用程序编程接口)时,需要进行速率限制。这是因为当您向公众公开 API 时,您会希望限制个人在特定时间内重复某项活动的次数。对您的系统没有管理权限的用户可能会使其陷入停顿。
速率限制通过拒绝超过设定限制的用户请求,使您的应用程序能够平稳运行。如果您有大量客户端,速率限制会实施公平使用政策,允许每个用户以极快的速度访问您的应用程序。速率限制还可以通过减少服务器上的拥堵来帮助您节省带宽费用。
通过在诸如 MySQL 之类的数据库中跟踪用户活动,是可以创建速率限制程序的。然而,由于此类数据需要从磁盘下载并根据定义的限制进行评估,因此当多人同时访问系统时,最终结果可能无法扩展。这不仅效率低下,而且关系型数据库管理解决方案本来就不是为此而设计的。
Redis 是制作速率限制器的绝佳选择,因为它作为内存数据库运行,并且已被证明是可靠的。在本教程中,我们将引导您完成在 Ubuntu 20.04 上使用 Redis 实现 PHP 速率限制.
让我们开始吧!
前提条件
要学习本教程,您需要满足以下条件:
-
最新版本的 Ubuntu 安装在您的系统上。
-
系统用户必须拥有 sudo 权限.
-
LAMP 栈。
-
请按照 如何设置 LAMP 栈 教程进行设置。
-
-
Redis 服务器。
-
请按照 如何安装和保护 Redis 服务器 指南在您的系统中设置 Redis。
-
步骤 1:安装 PHP 的 Redis 扩展
在开始之前,让我们更新存储库以避免软件包冲突:
|
1 |
sudo apt update |
接下来,安装 php-redis 扩展,这是一个使在 PHP 程序中使用 Redis 成为可能的软件包。运行以下 sudo 命令来安装 php-redis:
|
1 |
sudo apt install -y php-redis |
之后,重启 Apache 服务器以加载 php-redis 库:
|
1 |
sudo systemctl restart apache2 |
下一步是更新软件索引中的信息并安装 PHP 的 Redis 库。然后,我们将创建一个根据用户 IP 地址限制访问的 PHP 资源。
步骤 2:创建用于速率限制的 PHP Web 资源
在此步骤中,您将创建一个 demo.php 文件,位于 Web 服务器的根目录( /var/www/html/)中。该文件将向公众开放,用户将能够在他们首选的浏览器中启动该 URL。稍后,我们将使用 curl 命令来验证我们要使用的资源的可用性。用户可以在 15 秒的时间跨度内访问该示例资源文件三次。超过最大限制的尝试将抛出错误消息。
该文件的主要功能强烈依赖于 Redis 服务器。当用户首次访问资源时,文件中的 PHP 代码会根据用户的 IP 地址在 Redis 服务器上创建一个键。当用户再次访问该资源时,代码将尝试将用户的 IP 地址与 Redis 服务器中保存的键进行匹配,如果该键存在,则将其值增加 1。PHP 代码将持续检查新值是否已达到最大数量。
15 秒后,基于用户 IP 地址的 Redis 键将过期,系统将重新开始跟踪用户对 Web 资源的访问。打开 /var/www/html/demo.php 文件,在 nano 文本编辑器中:
|
1 |
sudo nano /var/www/html/demo.php |
然后,填写所有字段以初始化 Redis 类。不要忘记将 REDIS_PASSWORD 设置为正确的值:
|
1 2 3 4 |
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $redis->auth('REDIS_PASSWORD'); |
Redis->auth 支持 Redis 服务器明文身份验证。如果您在本地(通过 localhost)工作,这非常有效,但如果您面对的是远程 Redis 服务器,SSL 身份验证 是推荐的做法。
接下来,在同一个文件中,将以下变量设置为它们的默认值:
|
1 2 3 4 5 |
. . . $max_calls_limit = 3; $time_period = 15; $total_user_calls = 0; |
让我们详细了解一下这些语句:
-
$max_calls_limit:用户访问该资源不能超过此最大调用限制。
-
$time_period:这用作时间范围,以秒为单位计算。在这里,允许用户根据 中设置的限制来访问资源$max_calls_limit .
-
$total_user_calls:累加用户在给定时间段内请求访问 调用 限制的次数。
然后,添加以下代码以获取请求访问网页的用户的 IP 地址:
|
1 2 3 4 5 6 7 8 9 10 |
. . . 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)。如果您正在按照我们的教程进行操作,请记住使用这些 ID 代替 $user_ip 地址,如果您正在按照我们的教程进行操作。
在这里,用户的 IP 地址足以演示该概念。从前面的代码片段中获取用户的 IP 地址后,将以下代码块添加到您的文件中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
. . . 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 . " 秒"; |
以下是这些语句的概述:
-
if...else:该语句验证 Redis 服务器上是否定义了带有该 IP 地址的键。
-
如果未找到该键,如果 (!$redis->exists($user_ip_address)) {...},您可以使用以下命令设置该键并将其值定义为 1: $redis->set($user_ip_address, 1).
-
-
$redis->expire($user_ip_address, $time_period):设置该键在指定时间过期。在本教程中,我们将其设置为 15 秒。
-
如果在 Redis 键中未找到用户的 IP 地址,请将变量 $total_user_calls 值 设置为 1.
-
else {...}:该语句块使用 $redis->INCR($user_ip_address); 命令将 Redis 键的值递增 1。这将应用于与该键关联的每个 IP 地址。
-
注意:只有当该键已在 Redis 服务器中设置并被视为重复请求时,才能实现此操作。
-
-
$total_user_calls = $redis->get($user_ip_address):该语句通过验证 Redis 服务器上各自基于 IP 地址的键来检索总请求数。
-
if ($total_user_calls > $max_calls_limit) {... }..:此 if 语句用于验证是否超出限制。如果是,您可以使用以下内容通知用户: echo "用户" . $user_ip_address . " 超过限制。";.
最后,您可以使用以下语句通知用户其在指定时间段内的访问次数: echo "欢迎" . $user_ip_address . "总调用次数" . $total_user_calls . "在" . $time_period . "秒内"; 语句。
之后,在您的 /var/www/html/demo.php 文件中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $redis->auth('REDIS_PASSWORD'); $max_calls_limit = 3; $time_period = 15; $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_ip_address . " 超过限制。"; exit(); } } echo "欢迎 " . $user_ip_address . " 总调用次数 " . $total_user_calls . " 在 " . $time_period . " 秒内"; ?> |
保存并关闭 /var/www/html/demo.php 文件(在您完成修改后)。在 demo.php 网页上,您现在已经创建了限制用户速率所需的逻辑。让我们在下一步中测试我们的脚本。
步骤 3:运行 Redis 速率限制测试
您将在此步骤中使用 curl 命令来请求您在 步骤 2 中编写的 Web 资源。为了彻底测试该脚本,请发出一个请求该资源五次的命令。这可以通过向 添加占位符 URL 参数来实现 demo.php 文件的末尾。要执行 curl 指令五次,请使用值 ?[1-5] 在请求的末尾。
执行以下 curl 命令:
|
1 |
curl -H "Accept: text/plain" -H "Content-Type: text/plain" -X GET http://localhost/demo.php?[1-5] |
执行代码时,您应该会得到类似以下的内容:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[1/5]: http://localhost/demo.php?1 --> <stdout> --_curl_--http://localhost/demo.php?1 欢迎 127.0.0.1 总共 调用 已进行 1 在 15 秒 [2/5]: http://localhost/demo.php?2 --> <stdout> --_curl_--http://localhost/demo.php?2 欢迎 127.0.0.1 总共 调用 已进行 2 在 15 秒 [3/5]: http://localhost/demo.php?3 --> <stdout> --_curl_--http://localhost/demo.php?3 欢迎 127.0.0.1 总共 调用 已进行 3 在 15 秒 [4/5]: http://localhost/demo.php?4 --> <stdout> --_curl_--http://localhost/demo.php?4 用户 127.0.0.1 限制 已超出. [5/5]: http://localhost/demo.php?5 --> <stdout> --_curl_--http://localhost/demo.php?5 用户 127.0.0.1 限制 已超出. |
如您所见,前三个请求顺利完成。然而,第四个和第五个查询被您的脚本限制了速率。Redis 服务器很有可能正在降低人们进行查询的速度。
您在本指南中为下面列出的两个变量指定了较低的值:
|
1 2 3 4 |
... $max_calls_limit = 3; $time_period = 15; ... |
在生产环境中构建应用程序时,您可能需要考虑使用更大的数值,这取决于您认为人们使用它的频率。
在调整这些数字之前,最好先检查实时统计数据。在此示例中,如果您的服务器日志显示平均每个用户每 60 秒访问您的应用程序 1,000 次,则可以使用该数字作为要使用多少限制的示例。
结论
在本指南中,您学习了如何在 Ubuntu 20.04 上结合 PHP 使用 Redis 服务器。虽然本文演示了如何使用 Redis 进行速率限制,但您可以对其进行自定义以满足 Web 应用程序的需求。我们鼓励您探索实际示例,例如 Twitter 的最大请求限制, Google 的 Custom Search JSON API,以及其他类似文档,以增强您对速率限制的了解,并尝试使用不同的时间限制自己进行实验。
此外,您还可以从我们的博客:
- 在具有 Ubuntu 18.04 的 Kubernetes 集群上部署 PHP 应用程序
- 在 Ubuntu 20.04 上安装和保护 PHPmyadmin
- 如何在 Ubuntu 20.04 上安装 LEMP 栈(Linux Nginx MySQL PHP)
祝您编程愉快!
评论
暂无评论。发表第一条评论吧。