Skip to content

Rate Limit

Hyperf 的限流组件,支持多种算法(固定窗口、滑动窗口、令牌桶、漏桶)。

安装

bash
composer require friendsofhyperf/rate-limit

环境要求

  • Hyperf ~3.1.0
  • Redis

特性

  • 多种限流算法
    • 固定窗口
    • 滑动窗口
    • 令牌桶
    • 漏桶
  • 灵活的使用方式
    • 基于注解的限流(通过切面实现)
    • 自定义中间件支持
  • 多注解智能排序
    • 自动对多个 RateLimit 注解进行优先级排序
    • 根据严格程度智能排序(maxAttempts/decay 比率)
    • 更严格的限制优先检查,提升性能
  • 灵活的键生成
    • 默认基于方法/类的键
    • 支持自定义键和占位符
    • 支持数组键
    • 支持可调用键
  • 自定义响应
    • 自定义响应消息
    • 自定义 HTTP 响应码
  • 多 Redis 连接池支持

使用方式

方式一:使用注解

最简单的方式是使用 #[RateLimit] 属性:

php
<?php

namespace App\Controller;

use FriendsOfHyperf\RateLimit\Annotation\RateLimit;
use FriendsOfHyperf\RateLimit\Algorithm;

class UserController
{
    /**
     * 基础限流:60 秒内最多 60 次请求
     */
    #[RateLimit(maxAttempts: 60, decay: 60)]
    public function index()
    {
        // 你的代码
    }

    /**
     * 使用滑动窗口算法
     */
    #[RateLimit(
        maxAttempts: 100,
        decay: 60,
        algorithm: Algorithm::SLIDING_WINDOW
    )]
    public function api()
    {
        // 你的代码
    }

    /**
     * 自定义键,支持用户 ID 占位符
     */
    #[RateLimit(
        key: 'user:{userId}:action',
        maxAttempts: 10,
        decay: 3600
    )]
    public function create($userId)
    {
        // 你的代码
    }

    /**
     * 使用数组键
     */
    #[RateLimit(
        key: ['user', '{userId}', 'create'],
        maxAttempts: 5,
        decay: 60
    )]
    public function update($userId)
    {
        // 你的代码
    }

    /**
     * 自定义响应消息和状态码
     */
    #[RateLimit(
        maxAttempts: 5,
        decay: 60,
        response: 'Too many requests, please try again later.',
        responseCode: 429
    )]
    public function login()
    {
        // 你的代码
    }

    /**
     * 使用指定的 Redis 连接池
     */
    #[RateLimit(
        maxAttempts: 60,
        decay: 60,
        pool: 'rate_limit'
    )]
    public function heavyOperation()
    {
        // 你的代码
    }
}

注解参数

参数类型默认值说明
keystring|array''限流键。支持:'user:{user_id}', ['user', '{user_id}'], 或可调用函数
maxAttemptsint60允许的最大请求次数
decayint60时间窗口(秒)
algorithmAlgorithmAlgorithm::FIXED_WINDOW算法:fixed_window, sliding_window, token_bucket, leaky_bucket
pool?stringnull使用的 Redis 连接池
responsestring'Too Many Attempts, Please try again in %d seconds.'超出限流时的自定义响应
responseCodeint429超出限流时的 HTTP 状态码

使用 AutoSort 实现多限流规则智能排序

当同一个方法需要多个限流规则时(例如每分钟和每小时的限制),可以使用 AutoSort 注解自动按严格程度排序:

php
<?php

use FriendsOfHyperf\RateLimit\Annotation\RateLimit;
use FriendsOfHyperf\RateLimit\Annotation\AutoSort;

class ApiController
{
    /**
     * 多个限流规则智能排序
     * 更严格的限制(maxAttempts/decay 比率更小)优先检查
     */
    #[AutoSort]
    #[RateLimit(maxAttempts: 10, decay: 60)]      // 每分钟 10 次 - 优先检查
    #[RateLimit(maxAttempts: 100, decay: 3600)]    // 每小时 100 次 - 其次检查
    public function expensiveOperation()
    {
        // 你的代码
    }

    /**
     * 不使用 AutoSort 时,按声明顺序检查
     */
    #[RateLimit(maxAttempts: 100, decay: 3600)]    // 优先检查
    #[RateLimit(maxAttempts: 10, decay: 60)]       // 其次检查
    public function anotherOperation()
    {
        // 你的代码
    }
}

AutoSort 的优势:

  • 性能:严格的限制优先检查,避免不必要的宽松限制检查
  • 智能:自动根据限制严格程度(maxAttempts/decay 比率)计算优先级
  • 可选:仅在显式使用 AutoSort 的方法上生效
  • 向后兼容:现有代码无需修改即可继续工作

键占位符

key 参数支持动态占位符,会被方法参数替换:

php
// 命名占位符
#[RateLimit(key: 'user:{userId}:{action}')]
public function action($userId, $action)

// 数组格式(自动用 ':' 连接)
#[RateLimit(key: ['user', '{userId}', '{action}'])]
public function action($userId, $action)

方式二:使用中间件

对于 HTTP 请求,可以创建继承 RateLimitMiddleware 的自定义中间件:

php
<?php

namespace App\Middleware;

use FriendsOfHyperf\RateLimit\Middleware\RateLimitMiddleware;
use FriendsOfHyperf\RateLimit\Algorithm;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ApiRateLimitMiddleware extends RateLimitMiddleware
{
    // 重写默认属性
    protected int $maxAttempts = 100;
    protected int $decay = 60;
    protected Algorithm $algorithm = Algorithm::SLIDING_WINDOW;
    protected string $responseMessage = 'API rate limit exceeded';
    protected int $responseCode = 429;

    // 或自定义键解析
    protected function resolveKey(ServerRequestInterface $request): string
    {
        return 'api:' . $this->getClientIp();
    }
}

然后在配置中注册中间件:

php
// config/autoload/middlewares.php
return [
    'http' => [
        App\Middleware\ApiRateLimitMiddleware::class,
    ],
];

限流算法

固定窗口(默认)

最简单的算法,在固定时间窗口内计数请求。

php
#[RateLimit(algorithm: Algorithm::FIXED_WINDOW)]

优点:简单,内存高效
缺点:可能在窗口边界处允许突发请求

滑动窗口

比固定窗口更准确,均匀分布请求。

php
#[RateLimit(algorithm: Algorithm::SLIDING_WINDOW)]

优点:平滑突发流量,更准确
缺点:稍微复杂一些

令牌桶

允许突发流量,同时保持平均速率。

php
#[RateLimit(algorithm: Algorithm::TOKEN_BUCKET)]

优点:允许突发流量,灵活
缺点:需要更多配置

漏桶

以恒定速率处理请求,排队突发流量。

php
#[RateLimit(algorithm: Algorithm::LEAKY_BUCKET)]

优点:平滑输出速率,防止突发
缺点:可能延迟请求

自定义限流器

你可以通过实现 RateLimiterInterface 来实现自己的限流器:

php
<?php

namespace App\RateLimit;

use FriendsOfHyperf\RateLimit\Contract\RateLimiterInterface;

class CustomRateLimiter implements RateLimiterInterface
{
    public function tooManyAttempts(string $key, int $maxAttempts, int $decay): bool
    {
        // 你的实现
    }

    public function availableIn(string $key): int
    {
        // 你的实现
    }

    public function remaining(string $key, int $maxAttempts): int
    {
        // 你的实现
    }
}

异常处理

当超出限流时,会抛出 RateLimitException

php
<?php

try {
    $userController->index();
} catch (FriendsOfHyperf\RateLimit\Exception\RateLimitException $e) {
    // 超出限流
    $message = $e->getMessage();  // "Too Many Attempts. Please try again in X seconds."
    $code = $e->getCode();        // 429
}

配置

组件使用 Hyperf 的 Redis 配置。你可以在注解或中间件中指定使用的 Redis 连接池:

php
// 使用特定的 Redis 连接池
#[RateLimit(pool: 'rate_limit')]

确保在 config/autoload/redis.php 中配置 Redis 连接池:

php
return [
    'default' => [
        'host' => env('REDIS_HOST', 'localhost'),
        'port' => env('REDIS_PORT', 6379),
        'auth' => env('REDIS_AUTH', null),
        'db' => 0,
        'pool' => [
            'min_connections' => 1,
            'max_connections' => 30,
        ],
    ],
    'rate_limit' => [
        'host' => env('REDIS_HOST', 'localhost'),
        'port' => env('REDIS_PORT', 6379),
        'auth' => env('REDIS_AUTH', null),
        'db' => 1,
        'pool' => [
            'min_connections' => 5,
            'max_connections' => 50,
        ],
    ],
];

示例

示例 1:登录限流

限制登录尝试以防止暴力破解:

php
#[RateLimit(
    key: 'login:{email}',
    maxAttempts: 5,
    decay: 300, // 5 分钟
    response: 'Too many login attempts. Please try again after 5 minutes.',
    responseCode: 429
)]
public function login(string $email, string $password)
{
    // 登录逻辑
}

示例 2:API 端点限流

为不同的 API 端点设置不同的限流:

php
class ApiController
{
    // 公共 API:每分钟 100 次请求
    #[RateLimit(maxAttempts: 100, decay: 60)]
    public function public()
    {
        // 公共端点
    }

    // 高级 API:每分钟 1000 次请求
    #[RateLimit(maxAttempts: 1000, decay: 60)]
    public function premium()
    {
        // 高级端点
    }
}

示例 3:基于用户的限流

按用户限流:

php
#[RateLimit(
    key: ['user', '{userId}', 'action'],
    maxAttempts: 10,
    decay: 3600 // 1 小时
)]
public function performAction(int $userId)
{
    // 操作逻辑
}

示例 4:基于 IP 的限流

使用中间件按 IP 地址限流:

php
class IpRateLimitMiddleware extends RateLimitMiddleware
{
    protected function resolveKey(ServerRequestInterface $request): string
    {
        return 'ip:' . $this->getClientIp();
    }
}

示例 5:使用 AutoSort 的多级限流

使用 AutoSort 高效处理昂贵操作的多级限流:

php
class ReportController
{
    /**
     * 昂贵的报告生成,多级保护
     * AutoSort 确保优先检查严格的限制,提升性能
     */
    #[AutoSort]
    #[RateLimit(maxAttempts: 5, decay: 60, response: 'Too many requests. Max 5 per minute')]       // 紧急制动
    #[RateLimit(maxAttempts: 30, decay: 3600, response: 'Hourly limit exceeded. Max 30 per hour')] // 持续负载
    #[RateLimit(maxAttempts: 100, decay: 86400, response: 'Daily limit exceeded. Max 100 per day')] // 每日上限
    public function generateReport($reportType)
    {
        // 昂贵的报告生成逻辑
    }
}