在分散式系統和微服務架構中,API 的穩定性和可用性至關重要。為了保護後端服務不受惡意攻擊和流量高峰的影響,請求頻率限制(Rate Limiting)成為了一種常見的策略。
Hyperf 框架作為一款高效能的 PHP 框架,提供了豐富的元件來支援各種場景。
今天,我們要介紹的是 hyperf-throttle-requests
庫,一個專為 Hyperf 框架設計的請求頻率限流器。
hyperf-throttle-requests 庫簡介
hyperf-throttle-requests
是一個功能類似於 Laravel
框架中 throttle
中介軟體的元件。它能夠限制使用者在一定時間內的請求次數,超過限制則拒絕服務,從而保護後端服務不受惡意請求或高併發流量的衝擊。
最新版的 hyperf-throttle-requests 包,已經支援 hyperf 3.1 版本。
安裝
要在你的 Hyperf 專案中使用 hyperf-throttle-requests
,首先需要透過 Composer 安裝:
composer require pudongping/hyperf-throttle-requests:^3.0 -vvv
確保你的環境滿足以下要求:
- PHP版本 >= 8.1
- Hyperf框架版本 ~3.1.0
配置
安裝完成後,需要釋出配置檔案以進行個性化設定:
php bin/hyperf.php vendor:publish pudongping/hyperf-throttle-requests
這將在 config/autoload
目錄下生成 hyperf-throttle-requests.php
配置檔案。你可以在此檔案中設定限流器的各種引數,如儲存驅動、最大請求次數、時間視窗等。
配置說明
配置 | 預設值 | 說明 |
---|---|---|
storage | Pudongping\HyperfThrottleRequests\Storage\RedisStorage::class | 資料儲存驅動 |
maxAttempts | 60 | 在指定時間內允許的最大請求次數 |
decaySeconds | 60 | 單位時間(單位:s) |
prefix | '' | 計數器 key 字首,不填時,預設為:throttle: |
key | '' | 具體的計數器的 key (一般只有在某些特定場景下才會使用,比如假設訪問多個不同的路由時,均累加一個計數器) |
generateKeyCallable | [] | 生成計數器 key 的方法(預設以當前請求路徑加當前客戶端 IP 地址作為 key) |
tooManyAttemptsCallback | [] | 當觸發到最大請求次數時的回撥方法(預設會丟擲 Pudongping\HyperfThrottleRequests\Exception\ThrottleRequestsException 異常) |
使用
該元件提供了以下 3 種呼叫方式:
第一種:使用註解 Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests
該元件提供 Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests
註解,作用於類、類方法。
配置作用優先順序為:類方法上的註解配置 > 類上的註解配置 > config/autoload/hyperf-throttle-requests.php
> 註解預設配置
注意:只有使用註解呼叫時,才會使用 config/autoload/hyperf-throttle-requests.php
配置檔案中的配置項。
使用註解 Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests
作用於類上,示例:
<?php
/**
*
*
* Created by PhpStorm
* User: Alex
* Date: 2023-06-21 11:36
*/
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests;
#[AutoController(prefix: "throttle-requests")]
#[ThrottleRequests]
class ThrottleRequestsController
{
public function t1()
{
return [
'name' => 'alex'
];
}
public function t2()
{
return [
'name' => 'harry'
];
}
}
當提供 key
引數,且 key
引數的值為一個標量(不會變化的值)時,則該限流器同時作用於含有等值 key
上。舉個例子來說:在以下程式碼中Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests
註解作用於類上,也就意味著當訪問 /throttle-requests/t1
路由
和 /throttle-requests/t2
路由時,共享相同的配置資訊,由於此時的 key
引數的值為一個標量,也就意味著此時的現象是:在 15 秒內,當訪問/throttle-requests/t1
路由和 /throttle-requests/t2
路由時,總共只允許訪問 5 次。
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests;
#[AutoController(prefix: "throttle-requests")]
#[ThrottleRequests(key: "test-throttle", maxAttempts: 5, decaySeconds: 15, prefix: "TR:")]
class ThrottleRequestsController
{
public function t1()
{
return [
'name' => 'alex'
];
}
public function t2()
{
return [
'name' => 'harry'
];
}
}
使用註解 Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests
作用於類方法上,示例:
以下示例程式碼和以上示例程式碼,均為同樣的效果。
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests;
#[AutoController(prefix: "throttle-requests")]
class ThrottleRequestsController
{
#[ThrottleRequests(key: "test-throttle", maxAttempts: 5, decaySeconds: 15, prefix: "TR:")]
public function t1()
{
return [
'name' => 'alex'
];
}
#[ThrottleRequests(key: "test-throttle", maxAttempts: 5, decaySeconds: 15, prefix: "TR:")]
public function t2()
{
return [
'name' => 'harry'
];
}
}
第二種:使用 throttle_requests(string $rateLimits = '30,60', string $prefix = '', string $key = '', mixed $generateKeyCallable = [], mixed $tooManyAttemptsCallback = [])
助手函式
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
#[AutoController(prefix: "throttle-requests")]
class ThrottleRequestsController
{
public function t1()
{
throttle_requests(rateLimits: "5,15");
return [
'name' => 'alex'
];
}
}
第三種:直接呼叫 Pudongping\HyperfThrottleRequests\Handler\ThrottleRequestsHandler@handle()
方法
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Pudongping\HyperfThrottleRequests\Handler\ThrottleRequestsHandler;
use Hyperf\Context\ApplicationContext;
#[AutoController(prefix: "throttle-requests")]
class ThrottleRequestsController
{
public function t2()
{
ApplicationContext::getContainer()->get(ThrottleRequestsHandler::class)->handle(5, 15);
return [
'name' => 'harry'
];
}
}
關於計數器的 key
本質上,當傳入的 key
引數不為空字串時,則以傳入的 key
為主。當 key
為空字串,但是 generateKeyCallable
為一個可呼叫的回撥函式時,
則以回撥函式的返回值作為計數器的 key。否則預設為 sha1(當前路由地址路徑 . '|' . 當前客戶端 IP 地址)
作為 key。
其實質來說,generateKeyCallable
回撥函式就是去生成key
引數的值,這是為了方便使用者根據自己的需求動態的去生成計數器的鍵名。比如說:可能
當使用者登入之後,會加上 user_id 作為計數器的 key。
使用自定義 key 示例:
App\Controller\ThrottleRequestsController.php
檔案中
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Pudongping\HyperfThrottleRequests\Handler\ThrottleRequestsHandler;
use Hyperf\Context\ApplicationContext;
use Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests;
use App\Helper\ThrottleRequestsHelper;
use Hyperf\HttpServer\Contract\RequestInterface;
#[AutoController(prefix: "throttle-requests")]
class ThrottleRequestsController
{
public function __construct(protected RequestInterface $request)
{
}
#[ThrottleRequests(generateKeyCallable: [ThrottleRequestsHelper::class, "generateKeyCallable"])]
public function t1()
{
return [
'name' => 'alex'
];
}
public function t2()
{
ApplicationContext::getContainer()
->get(ThrottleRequestsHandler::class)
->handle(
5,
15,
generateKeyCallable: [$this, 'generateKeyCallable']
);
return [
'name' => 'harry'
];
}
public function generateKeyCallable()
{
return 'alex_' . $this->request->url();
}
}
觸發訪問頻率限制
當限流被觸發時,預設會丟擲 Pudongping\HyperfThrottleRequests\Exception\ThrottleRequestsException
異常,可以透過捕獲異常
或者配置 tooManyAttemptsCallback
限流回撥處理。例如:
App\Controller\ThrottleRequestsController.php
檔案中
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Pudongping\HyperfThrottleRequests\Handler\ThrottleRequestsHandler;
use Hyperf\Context\ApplicationContext;
use Pudongping\HyperfThrottleRequests\Annotation\ThrottleRequests;
use App\Helper\ThrottleRequestsHelper;
#[AutoController(prefix: "throttle-requests")]
class ThrottleRequestsController
{
#[ThrottleRequests(tooManyAttemptsCallback: [ThrottleRequestsHelper::class, 'tooManyAttemptsCallback'])]
public function t1()
{
return [
'name' => 'alex'
];
}
public function t2()
{
ApplicationContext::getContainer()
->get(ThrottleRequestsHandler::class)
->handle(
5,
15,
tooManyAttemptsCallback: function () {
var_dump('請求過於頻繁');
throw new \RuntimeException('請求過於頻繁', 429);
}
);
return [
'name' => 'harry'
];
}
}
App\Helper\ThrottleRequestsHelper.php
檔案中
<?php
declare(strict_types=1);
namespace App\Helper;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Context\ApplicationContext;
class ThrottleRequestsHelper
{
public function __construct(protected RequestInterface $request)
{
}
public static function generateKeyCallable()
{
$request = ApplicationContext::getContainer()->get(RequestInterface::class);
return $request->getUri()->getPath();
}
public static function tooManyAttemptsCallback()
{
var_dump('Too Many Attempts.');
throw new \RuntimeException('請求過於頻繁', 429);
}
}
結語
hyperf-throttle-requests
庫為 Hyperf 框架的開發者提供了一個強大的請求頻率限流工具,幫助他們保護後端服務不受惡意請求的影響。
希望本文能夠幫助你瞭解並開始使用 hyperf-throttle-requests
,為你的專案增加一層安全保障。