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

简介: `hyperf-throttle-requests` 是专为 Hyperf 框架设计的请求频率限流库,适用于分布式系统和微服务架构,能有效防止恶意攻击和流量高峰对后端服务的影响。通过限制用户在一定时间内的请求次数,确保服务的稳定性和可用性。该库支持注解、助手函数及直接调用三种使用方式,并提供了灵活的配置选项,如存储驱动、最大请求次数等。最新版已兼容 Hyperf 3.1 版本,安装简便,配置灵活,是保护后端服务的理想选择。

在分布式系统和微服务架构中,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,为你的项目增加一层安全保障。

相关文章
|
NoSQL 算法 安全
PHP实现令牌桶限流Redis list列表 Lpush rpop 实现令牌桶 - 限流 PHP实例
PHP实现令牌桶限流Redis list列表 Lpush rpop 实现令牌桶 - 限流 PHP实例
367 0
PHP实现令牌桶限流Redis list列表 Lpush rpop 实现令牌桶 - 限流 PHP实例
|
4月前
|
安全 关系型数据库 MySQL
PHP与MySQL交互:从入门到实践
【9月更文挑战第20天】在数字时代的浪潮中,掌握PHP与MySQL的互动成为了开发动态网站和应用程序的关键。本文将通过简明的语言和实例,引导你理解PHP如何与MySQL数据库进行对话,开启你的编程之旅。我们将从连接数据库开始,逐步深入到执行查询、处理结果,以及应对常见的挑战。无论你是初学者还是希望提升技能的开发者,这篇文章都将为你提供实用的知识和技巧。让我们一起探索PHP与MySQL交互的世界,解锁数据的力量!
|
2月前
|
前端开发 关系型数据库 MySQL
PHP与MySQL动态网站开发实战指南####
【10月更文挑战第21天】 本文将深入浅出地探讨如何使用PHP与MySQL构建一个动态网站,从环境搭建到项目部署,全程实战演示。无论你是编程新手还是希望巩固Web开发技能的老手,都能在这篇文章中找到实用的技巧和启发。我们将一起探索如何通过PHP处理用户请求,利用MySQL存储数据,并最终呈现动态内容给用户,打造属于自己的在线平台。 ####
65 0
|
4月前
|
NoSQL 关系型数据库 MySQL
不是 PHP 不行了,而是 MySQL 数据库扛不住啊
【9月更文挑战第8天】这段内容讨论了MySQL在某些场景下面临的挑战及其原因,并指出这些问题不能完全归咎于MySQL本身。高并发读写压力、数据量增长以及复杂查询和事务处理都可能导致性能瓶颈。然而,应用程序设计不合理、系统架构不佳以及其他数据库选择和优化策略不足也是重要因素。综合考虑这些方面才能有效解决性能问题,而MySQL通过不断改进和优化,仍然是许多应用场景中的可靠选择。
176 9
|
5月前
|
存储 SQL 关系型数据库
PHP与MySQL交互的奥秘
【8月更文挑战第29天】在编程的世界里,PHP和MySQL就像是一对默契的舞伴,共同演绎着数据的交响曲。本文将带你探索它们之间的互动,从连接数据库到执行查询,再到处理结果,每一步都充满了节奏与和谐。我们将一起走进这段代码的旅程,感受数据流动的魅力。
|
1月前
|
存储 关系型数据库 MySQL
PHP与MySQL动态网站开发:从基础到实践####
本文将深入探讨PHP与MySQL的结合使用,展示如何构建一个动态网站。通过一系列实例和代码片段,我们将逐步了解数据库连接、数据操作、用户输入处理及安全防护等关键技术点。无论您是初学者还是有经验的开发者,都能从中获益匪浅。 ####
|
2月前
|
安全 关系型数据库 MySQL
PHP与MySQL动态网站开发实战指南####
——深入探索LAMP栈下的高效数据交互与处理技巧 ####
|
1月前
|
关系型数据库 MySQL PHP
php实现一个简单的MySQL分页
通过本文的详细步骤和代码示例,我们实现了一个简单的PHP MySQL分页功能。主要步骤包括计算总记录数、设置分页参数、查询当前页的数据以及生成分页链接。这种分页方式适用于大多数Web应用,能够有效提升用户体验和页面响应速度。
31 4
|
2月前
|
关系型数据库 MySQL PHP
PHP与MySQL动态网站开发实战指南####
深入探索PHP与MySQL的协同工作机制,本文旨在通过一系列实战案例,揭示构建高效、稳定且用户友好的动态网站的秘诀。从环境搭建到数据交互,再到最佳实践分享,本文为开发者提供了一条清晰的学习路径,助力其在LAMP(Linux, Apache, MySQL, PHP/Perl/Python)栈上实现技术飞跃。 ####
|
2月前
|
关系型数据库 MySQL PHP
PHP与MySQL的无缝集成:构建动态网站的艺术####
本文将深入探讨PHP与MySQL如何携手合作,为开发者提供一套强大的工具集,以构建高效、动态且用户友好的网站。不同于传统的摘要概述,本文将以一个生动的案例引入,逐步揭示两者结合的魅力所在,最终展示如何通过简单几步实现数据驱动的Web应用开发。 ####