Redis,也稱為 Remote Dictionary Server,是一款開源的記憶體內資料庫。它是一個運行在伺服器’的 RAM 上的資料結構儲存系統,這比最快的固態硬碟(SSD)還要快得多。因此,Redis 的響應速度非常快,非常適合用於速率限制。
速率限制 限制了使用者向伺服器請求資源的次數。許多服務使用速率限制來防止服務濫用,例如當使用者試圖以過大負載壓垮伺服器時。例如,當使用 PHP 來為您的網頁應用程式開發公開的 API (Application Programming Interface) 時,就需要進行速率限制。這是因為當您向大眾公開 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 網頁資源
在此步驟中,您’將建立一個 demo.php 檔案,位於您網頁伺服器’的根目錄( /var/www/html/)。此檔案將對大眾公開,使用者將能夠在他們偏好的網頁瀏覽器中開啟該 URL。稍後,我們將使用 curl 指令來驗證我們想要使用的資源的可存取性。使用者可以在 15 秒內存取該範例資源檔案三次。超過最大限制的嘗試將會拋出錯誤訊息。
此檔案’的主要功能強烈依賴於 Redis 伺服器。當使用者首次存取資源時,檔案中的 PHP 程式碼會根據使用者’的 IP 位址在 Redis 伺服器上建立一個鍵。當使用者再次存取資源時,程式碼將嘗試將使用者’的 IP 位址與 Redis 伺服器中儲存的鍵進行比對,如果該鍵存在,則將其值加一。PHP 程式碼將持續檢查新值是否已達到最大數量。
15 秒後,基於使用者 IP 位址的 Redis 鍵值將會過期,並將重新開始追蹤使用者對網路資源的存取。開啟 /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 位址記錄其操作。如果您的伺服器上有需要驗證的受保護資源,您可以使用其使用者名稱或存取權杖(access token)來追蹤使用者的操作。
在我們的指南中,登入您系統的每個使用者都將被分配一個唯一識別碼(例如:客戶 ID、開發者 ID、廠商 ID,甚至是使用者 ID)。如果您正在跟著我們的教學進行,請記得使用這些 ID,而不是 $user_ip address(如果您正跟著我們的教學進行)。
在這裡,使用者的 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_ip_address . " 超過限制。"; exit(); } } echo "歡迎 " . $user_ip_address . " 總共呼叫了 " . $total_user_calls . " 次,在 " . $time_period . " 秒"; |
以下是這些陳述式的概述:
-
if...else: 此陳述式會驗證 Redis 伺服器上是否已定義具有該 IP 地址的鍵。
-
如果找不到該鍵,if (!$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 中編寫的網頁資源。為了徹底測試該指令碼,請發送單個指令來請求該資源五次。這可以透過在 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 進行速率限制,但您可以對其進行自訂以滿足您網頁應用程式的需求。我們鼓勵您探索實際範例,例如 Twitter 的最大請求限制, Google 的 Custom Search JSON API,以及其他類似的文件,以增進您對速率限制的了解,並嘗試使用不同的時間限制自己進行實驗。
此外,您還可以從我們的 部落格:
- 在具有 Ubuntu 18.04 的 Kubernetes 叢集上部署 PHP 應用程式
- 在 Ubuntu 20.04 上安裝並保護 PHPmyadmin
- 如何在 Ubuntu 20.04 上安裝 LEMP Stack (Linux Nginx MySQL PHP)
祝您運算愉快!
留言
目前尚無留言。成為第一個留言的人吧。