作者选择了 Apache Software Foundation作为 Write for Donations计划的一部分接受捐款。
介绍
Redis(Re**mote Dictionary Server)是一个内存开源软件,它是一种使用服务器的RAM的数据结构存储,比最快的固态驱动器(SSD)快几倍。
率限制是一种技术,限制了用户可以从服务器请求资源的次数,许多服务都实施率限制,以防止用户可能试图在服务器上放置太多的负载时滥用服务。
例如,当您在使用 PHP为您的 Web 应用程序实现公共 API(应用程序编程接口)时,您需要某种形式的率限制。原因是当您向公众发布 API 时,您希望对应用程序用户可以在特定时间框架内重复操作的次数进行控制。
拒绝超过一定限度的用户请求允许您的应用程序顺利运行. 如果您有许多客户,则率限制执行公平使用政策,允许每个客户获得高速访问您的应用程序。
通过在MySQL等数据库中记录用户活动来编码速率限制模块可能是实用的,但是,当许多用户访问系统时,最终产品可能无法扩展,因为数据必须从磁盘中提取并与设置限制进行比较。
由于Redis作为内存数据库工作,因此它是创建率限制器的合格候选人,并且已被证明可靠(https://redislabs.com/redis-best-practices/basic-rate-limiting/)。
在本教程中,您将在Ubuntu 20.04服务器上实施PHP脚本以限制率。
前提条件
在你开始之前,你需要以下几点:
- 一个 Ubuntu 20.04 服务器和一个非 root 用户具有 sudo 特权。 请参阅 初始服务器设置与 Ubuntu 20.04 指南来设置您的服务器并创建一个新的用户
- 一个 LAMP 堆栈。 遵循 [如何在 Ubuntu 20.04 上安装 Linux, Apache, MySQL, PHP (LAMP) 堆栈(https://andsky.com/tech/tutorials/how-to-install-linux-apache-mysql-php-lamp-stack-on-ubuntu-20-04) )。 在本指南中,您可以跳过 步骤 4 — 创建一个虚拟主机为您的网站 并使用已在 Apache 的安装中创建的默认虚拟主机
- 一个 Redis 服务器。 通过遵循 [如何在 Ubuntu 20.04 上安装和安全 Redis - 快速启动([INK
步骤 1 — 安装PHP的Redis库
首先,您将通过更新您的Ubuntu服务器包库索引开始。 然后,安装php-redis
扩展。 这是一个允许您在PHP代码中实现Redis的库。 要做到这一点,请运行以下命令:
1sudo apt update
2sudo apt install -y php-redis
接下来,重新启动Apache服务器来加载php-redis
库:
1sudo systemctl restart apache2
一旦您更新了软件信息索引并安装了 PHP 的 Redis 库,您现在将创建一个基于 IP 地址阻止用户访问的 PHP 资源。
步骤 2 — 构建一个PHP Web 资源以限制率
在此步骤中,您将在您的 Web 服务器的 root 目录(/var/www/html/
)中创建一个 test.php
文件. 此文件将向公众开放,用户可以在 Web 浏览器中输入其地址以运行它。
示例资源文件允许用户在 10 秒的时间范围内访问它三次. 试图超越限制的用户将收到一个错误通知他们已经被限制。
此文件的核心功能在很大程度上取决于Redis服务器. 当用户首次请求资源时,该文件中的PHP代码将根据用户的IP地址在Redis服务器上创建一个密钥。
当用户再次访问该资源时,PHP代码将试图将用户的IP地址与存储在Redis服务器中的密钥匹配,并增加值,如果密钥存在的话。
基于用户 IP 地址的 Redis 密钥将在 10 秒后到期;此时间过后,用户访问 Web 资源的登录将再次开始。
首先,打开 /var/www/html/test.php
文件:
1sudo nano /var/www/html/test.php
接下来,输入以下信息来初始化 Redis 类. 请记住输入适当的值为 REDIS_PASSWORD
:
1[label /var/www/html/test.php]
2<?php
3
4$redis = new Redis();
5$redis->connect('127.0.0.1', 6379);
6$redis->auth('REDIS_PASSWORD');
$redis->auth
实现了对 Redis 服务器的简单文本身份验证,在您在本地工作(通过 localhost
),但如果您正在使用远程 Redis 服务器,请考虑使用 SSL 身份验证。
接下来,在同一个文件中,初始化以下变量:
1[label /var/www/html/test.php]
2. . .
3$max_calls_limit = 3;
4$time_period = 10;
5$total_user_calls = 0;
你已经定义了:
$max_calls_limit
:是用户可以访问资源的最大呼叫次数$time_period
:定义了用户允许通过$max_calls_limit
访问资源的秒数。
接下来,添加以下代码来检索请求 Web 资源的用户的 IP 地址:
1[label /var/www/html/test.php]
2. . .
3if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
4 $user_ip_address = $_SERVER['HTTP_CLIENT_IP'];
5} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
6 $user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
7} else {
8 $user_ip_address = $_SERVER['REMOTE_ADDR'];
9}
虽然此代码使用用户的 IP 地址用于演示目的,但如果您在服务器上有需要身份验证的受保护资源,则可以使用用户名或访问令牌记录用户的活动。
在这种情况下,每个登录到您的系统的用户都会有一个唯一的标识符(例如,客户 ID、开发人员 ID、供应商 ID 甚至是用户 ID)。
对于本指南,用户的IP地址足以证明这个概念,因此,一旦您在上一个代码片段中获取了用户的IP地址,请将下一个代码块添加到您的文件中:
1[label /var/www/html/test.php]
2. . .
3if (!$redis->exists($user_ip_address)) {
4 $redis->set($user_ip_address, 1);
5 $redis->expire($user_ip_address, $time_period);
6 $total_user_calls = 1;
7} else {
8 $redis->INCR($user_ip_address);
9 $total_user_calls = $redis->get($user_ip_address);
10 if ($total_user_calls > $max_calls_limit) {
11 echo "User " . $user_ip_address . " limit exceeded.";
12 exit();
13 }
14}
15
16echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";
在此代码中,您使用一个if...else
声明来检查 Redis 服务器上是否有与 IP 地址定义的密钥. 如果密钥不存在,则您将其设置为如果($user_ip_address,1)
,并使用代码$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
。
声明 $total_user_calls = $redis->get($user_ip_address);
通过在 Redis 服务器上检查其基于 IP 地址的密钥来检索用户的总请求。
至檔案的末尾,你會使用「...if($total_user_calls > $max_calls_limit) {... }..」聲明來檢查限度是否超過;如果是這樣,你會用「Echo」警告使用者「User」「$total_user_ip_address」「超過限度」。
添加所有代码后,您的 /var/www/html/test.php
文件将如下:
1[label /var/www/html/test.php]
2<?php
3$redis = new Redis();
4$redis->connect('127.0.0.1', 6379);
5$redis->auth('REDIS_PASSWORD');
6
7$max_calls_limit = 3;
8$time_period = 10;
9$total_user_calls = 0;
10
11if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
12 $user_ip_address = $_SERVER['HTTP_CLIENT_IP'];
13} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
14 $user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
15} else {
16 $user_ip_address = $_SERVER['REMOTE_ADDR'];
17}
18
19if (!$redis->exists($user_ip_address)) {
20 $redis->set($user_ip_address, 1);
21 $redis->expire($user_ip_address, $time_period);
22 $total_user_calls = 1;
23} else {
24 $redis->INCR($user_ip_address);
25 $total_user_calls = $redis->get($user_ip_address);
26 if ($total_user_calls > $max_calls_limit) {
27 echo "User " . $user_ip_address . " limit exceeded.";
28 exit();
29 }
30}
31
32echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds";
当你完成了编辑 /var/www/html/test.php
文件时,保存并关闭它。
你现在已经编码了必要的逻辑来限制用户在test.php
网页资源的评级. 在下一步,你将测试你的脚本。
步骤 3 – 测试 Redis 率限制
在此步骤中,您将使用curl
命令来请求您在 Step 2中编码的网页资源. 要完全检查脚本,您将在单个命令中请求该资源五次。
运行以下命令:
1curl -H "Accept: text/plain" -H "Content-Type: text/plain" -X GET http://localhost/test.php?[1-5]
运行代码后,您将收到类似于以下的输出:
1[secondary_label Output]
2[1/5]: http://localhost/test.php?1 --> <stdout>
3--_curl_--http://localhost/test.php?1
4Welcome 127.0.0.1 total calls made 1 in 10 seconds
5[2/5]: http://localhost/test.php?2 --> <stdout>
6--_curl_--http://localhost/test.php?2
7Welcome 127.0.0.1 total calls made 2 in 10 seconds
8[3/5]: http://localhost/test.php?3 --> <stdout>
9--_curl_--http://localhost/test.php?3
10Welcome 127.0.0.1 total calls made 3 in 10 seconds
11[4/5]: http://localhost/test.php?4 --> <stdout>
12--_curl_--http://localhost/test.php?4
13User 127.0.0.1 limit exceeded.
14[5/5]: http://localhost/test.php?5 --> <stdout>
15--_curl_--http://localhost/test.php?5
16User 127.0.0.1 limit exceeded.
正如你会注意到的,前三个请求没有问题,但是,你的脚本限制了第四和第五个请求的率,这证实了Redis服务器正在限制用户的请求的率。
在本指南中,您为以下两个变量设置了低值:
1[label /var/www/html/test.php]
2...
3$max_calls_limit = 3;
4$time_period = 10;
5...
当您在生产环境中设计应用程序时,您可能会考虑更高的值,这取决于您期望用户频繁打击您的应用程序。
例如,如果您的服务器日志显示,平均用户每 60 秒点击您的应用程序 1,000 次,则可以将这些值用作引导用户的基准。
为了把事情置于一个更好的视角,以下是一些实际实例的利率限制实施(截至2021年):
- Twitter 只允许 100,000 个请求每天到他们的
/statuses/mentions_timeline
和/statuses/user_timeline
终端 - DigitalOcean 允许每小时 5,000 个请求在他们的 API 终端为每位用 OAuth 符号身份验证的用户
- Google Custom Search JSON API允许每天 100 个搜索查询免费
结论
本教程在 Ubuntu 20.04 服务器上实施了 PHP 脚本以限制率,以防止您的 Web 应用程序意外或恶意过度使用。
您可能想为生产使用安全保护您的Apache服务器;请遵循如何在Ubuntu 20.04上安全保护Apache
教程(LINK0)。
您也可以考虑阅读 Redis 如何作为数据库缓存。 尝试我们的 [如何在 Ubuntu 20.04 上使用 PHP 设置 Redis 作为 MySQL 缓存] 教程。