hyperf-throttle-requests,一個超牛的 PHP 限流神器

左诗右码發表於2024-11-30

在分散式系統和微服務架構中,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 配置檔案。你可以在此檔案中設定限流器的各種引數,如儲存驅動、最大請求次數、時間視窗等。

配置說明

配置預設值說明
storagePudongping\HyperfThrottleRequests\Storage\RedisStorage::class資料儲存驅動
maxAttempts60在指定時間內允許的最大請求次數
decaySeconds60單位時間(單位: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,為你的專案增加一層安全保障。

相關文章