fiberphp / limiter
🚦 FiberPHP 限流器 —— Redis Hash 固定窗口计数,支持 IP 白名单(单 IP、CIDR、通配符),协程安全。
Requires
- php: >=8.3
- fiberphp/framework: dev-master
- fiberphp/redis: dev-master
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-08-24 11:36:28 UTC
README
基于 Redis 的限流器组件,适用于 FiberPHP 框架。采用固定窗口算法,通过 Redis Hash 分桶计数,热路径仅需一次 HINCRBY 调用,性能开销极低。内置 IP 白名单机制(支持单 IP、CIDR、通配符三种匹配方式)。
特性
- 固定窗口限流算法,窗口按 TTL 对齐到 epoch
- Redis Hash 按天分桶存储,热路径仅一次
HINCRBY,过期时间通过 Lua 脚本惰性延长(只增不减) - IP 白名单支持单 IP、CIDR(如
192.168.0.0/16)、通配符(如192.168.*.*)三种格式 - 提供
attempt()(返回布尔值)与check()(超限自动抛异常)两种调用方式 - 异常类与 HTTP 状态码可配置,默认返回
429 Too Many Requests - 懒初始化:首次调用自动初始化,无需 ServiceProvider
安装
在项目根目录的 composer.json 中添加仓库与依赖:
{
"require": {
"fiberphp/limiter": "*"
},
"repositories": [
{
"type": "path",
"url": "path/to/haohai/*",
"options": { "symlink": true, "relative": false }
}
]
}
执行安装:
composer require fiberphp/limiter
安装完成后,配置文件会被发布到项目的 config/limiter/app.php。
配置说明
配置文件位于 config/limiter/app.php,通过 config('limiter.app.xxx') 访问。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enable | bool | true | 是否启用限流器 |
ip_whitelist | array | [] | 不受频率限制的 IP 列表,支持单 IP、CIDR、通配符 |
exception | string | LimitException::class | 超出限流时抛出的异常类,需继承 BusinessException |
配置示例:
<?php
use FiberPHP\Limiter\LimitException;
return [
'enable' => true,
// 这些 ip 的请求不做频率限制
'ip_whitelist' => [
'127.0.0.1', // 单 IP
'10.0.0.0/8', // CIDR
'192.168.*.*', // 通配符
],
'exception' => LimitException::class,
];
注意:组件通过
FiberPHP\Redis\Sync客户端连接 Redis,连接名为limiter,需在 Redis 组件(fiberphp/redis)的配置中预先定义名为limiter的连接。
使用方法
1. 超限自动抛异常
check() 会在超出限制时自动抛出配置中指定的异常,适合在中间件或控制器入口直接调用:
use FiberPHP\Limiter\Limiter;
// 每个 IP 每分钟(60 秒)最多 100 次请求,超限抛出 LimitException(HTTP 429)
$ip = $request->getRealIp();
Limiter::check('ip:' . $ip, 100, 60, '请求过于频繁,请稍后再试');
2. 手动判断是否超限
attempt() 返回布尔值,由调用方自行决定后续逻辑:
use FiberPHP\Limiter\Limiter;
use FiberPHP\Limiter\LimitException;
// 同一用户每秒最多调用 5 次发短信接口
$key = 'sms:' . $userId;
if (!Limiter::attempt($key, 5, 1)) {
throw new LimitException('操作过于频繁', 429);
}
// 继续业务逻辑
3. 结合 IP 白名单
在限流前先判断 IP 是否在白名单,白名单内的请求直接放行:
use FiberPHP\Limiter\Limiter;
$ip = $request->getRealIp();
if (!Limiter::isIpWhiteListed($ip)) {
Limiter::check('ip:' . $ip, 100, 60, '请求过于频繁,请稍后再试');
}
// 处理请求
限流算法说明
组件采用固定窗口算法(Fixed Window):
- 窗口对齐:窗口结束时间按 TTL 对齐到 epoch(Unix 时间戳原点)。例如 TTL 为 60 秒时,窗口边界为每分钟的整点(
00:00、01:00、02:00...),所有落在同一窗口内的请求共享同一计数。 - 按天分桶:Redis 端以本地日期(
Y-m-d)为单位,每天使用一个 Hash(limiter-YYYY-MM-DD),字段格式为{key}-{窗口结束时间}-{ttl},值为该窗口内的累计请求数。 - 热路径优化:计数仅执行一次
HINCRBY。过期时间的延长通过 Lua 脚本惰性触发,且只在本地缓存认为需要延长时才调用,脚本保证过期时间只增不减。 - 自动回收:Hash 的过期时间覆盖当天最后一个窗口的结束时刻,过期后由 Redis 自动清理;本地进程内的过期时间缓存每 60 秒清理一次。
相比滑动窗口,固定窗口实现简单、性能更高,但在窗口边界处可能出现瞬时双倍流量(相邻两个窗口各放过 limit 次)。如对边界突刺敏感,可结合业务适当调小 limit 或缩短 ttl。
IP 白名单机制
isIpWhiteListed() 依次匹配 ip_whitelist 配置中的每一项,命中任意一条即返回 true。匹配规则由 ipInRange() 实现,支持三种格式:
| 格式 | 示例 | 说明 |
|---|---|---|
| 单 IP | 127.0.0.1 | 完全相等的 IPv4 地址 |
| CIDR | 192.168.0.0/16 | 通过子网掩码按位匹配网段 |
| 通配符 | 192.168.*.* | * 匹配任意一段(一位或多位数字) |
匹配仅针对 IPv4。白名单在首次调用时一次性懒加载到进程内存,运行期直接读取内存,无额外开销。
异常处理
超限时默认抛出 FiberPHP\Limiter\LimitException,它继承自 FiberPHP\Exception\BusinessException,HTTP 状态码为 429:
namespace FiberPHP\Limiter;
use FiberPHP\exception\BusinessException;
class LimitException extends BusinessException
{
protected int $httpCode = 429;
}
可通过 exception 配置项替换为自定义异常类(需继承 BusinessException 并能被 (string) 实例化构造)。调用 check() 时传入的 message 会作为异常消息:
Limiter::check($key, 100, 60, '自定义提示信息');
若不希望抛异常,改用 attempt() 自行处理:
if (!Limiter::attempt($key, 100, 60)) {
// 自定义响应
return json(['code' => 429, 'msg' => 'Too Many Requests'])->withStatus(429);
}
目录结构
fiberphp/limiter/
├── src/
│ ├── config/
│ │ └── app.php # 默认配置,安装时发布到 config/limiter/app.php
│ ├── Install.php # 安装钩子,定义配置路径
│ ├── LimitException.php # 限流异常,HTTP 429
│ ├── Limiter.php # 限流器核心,提供 attempt/check/白名单能力
│ └── Redis.php # Redis 存储实现,固定窗口计数与过期管理
├── composer.json
└── README.md
依赖说明
| 依赖 | 说明 |
|---|---|
| PHP >= 8.2 | 运行环境要求 |
fiberphp/http | 提供 HTTP 请求与异常处理基础 |
fiberphp/redis | 提供 FiberPHP\Redis\Sync 同步 Redis 客户端,需配置名为 limiter 的连接 |
fiberphp/framework (dev) | 开发依赖,提供框架运行环境与 InstallHelper |
命名空间为 FiberPHP\Limiter\(PSR-4,对应 src/ 目录)。