hyperf中keys比普通php更危险,因其协程redis客户端执行keys会同步阻塞当前协程数百毫秒至秒级,导致该协程无法处理请求,高并发下易引发协程池耗尽与超时雪崩;而普通php是多进程,仅影响单个请求。

Hyperf 里不能直接用 KEYS 查大 Key,必须用 SCAN 分批处理,否则一执行就卡住整个协程调度器——这不是慢,是服务级中断。
为什么 Hyperf 中 KEYS 比普通 PHP 更危险
Hyperf 默认使用 Swoole 协程 Redis 客户端(如 co\Redis 或 hyperf/redis 封装),所有 Redis 调用默认在协程内同步阻塞。一旦执行 KEYS *,主线程不卡,但当前协程会挂起直到命令返回;而这个“等待”可能持续数百毫秒甚至秒级,期间该协程无法响应任何请求,且若并发调用多路 KEYS,极易引发协程池耗尽、超时雪崩。
更隐蔽的问题是:很多开发者在 Command 或定时任务里写 KEYS 脚本,误以为“只在后台跑”,却忘了这些命令仍走协程 Redis 客户端,照样拖垮线上服务。
-
KEYS在 Redis 6.0+ 仍是同步全量扫描,Hyperf 无法绕过这一底层限制 - Hyperf 的
redis-cli --bigkeys也不行——它本质是启动新进程调用原生命令,但该命令本身就会在目标节点上触发KEYS-like 全量遍历 - 某些 SDK(如旧版
hyperf/redis)对游标类型校验松散,传字符串"0"可能被静默转成0,但部分 Redis 服务端版本会报ERR invalid cursor
Hyperf 中正确用 SCAN 检测大 Key 的三步写法
核心不是“替换命令”,而是重构逻辑:把一次性收集 → 改为流式采样 + 异步评估 + 内存可控聚合。
- 用
SCAN游标循环获取 key 列表,每次最多取COUNT 500,避免单次网络包过大或协程挂起太久 - 对每个 key,用
MEMORY USAGE(非DEBUG OBJECT)查内存占用,MEMORY USAGE是非阻塞命令,且结果更贴近真实分配 - 每批最多处理 20–50 个 key 后主动
co::sleep(0.01)让出协程,防止长时间独占调度器;结果存入Swoole\Table或临时文件,而非 PHP 数组累积
示例片段(基于 hyperf/redis):
$cursor = '0';
$bigKeys = [];
do {
$result = $this->redis->scan($cursor, ['MATCH' => 'user:*', 'COUNT' => 500]);
[$cursor, $keys] = $result;
foreach ($keys as $key) {
// 非阻塞查内存,失败跳过
$mem = $this->redis->command('MEMORY', ['USAGE', $key]) ?: 0;
if ($mem > 1024 * 1024) { // >1MB
$bigKeys[] = [$key, $mem];
}
}
// 主动让出,防协程饿死
if (count($bigKeys) % 30 === 0) {
co::sleep(0.01);
}
} while ($cursor !== '0');
SCAN 在 Hyperf 里容易漏掉的三个细节
这些不是“语法错误”,而是上线后才暴露的隐性故障点:
- 游标比较必须用字符串
=== '0',不能用整数== 0—— Swoole Redis 返回的游标始终是字符串,PHP 自动转换会导致循环不退出 -
MATCH模式不能以*开头(如*:order),否则 Redis 无法利用内部哈希桶优化,实际扫描效率退化到接近KEYS - 不要在
go()协程里嵌套SCAN循环再wait()—— 这等于把异步压回同步,失去协程优势;应改用Channel或Parallel分片并行扫不同前缀
真正要清理大 Key 时,别只删 key
检测出大 Key 只是第一步。Hyperf 应用中直接 DEL 一个 50MB 的 Hash,照样会让 Redis 主线程卡顿 200ms+,进而拖慢所有协程请求。必须降级操作:
- 对
HASH:用HSCAN分批读 field,每次HDEL不超过 10 个字段,中间加co::sleep(0.005) - 对
LIST:优先用LRANGE+LTRIM截断,而不是LRANGE全取再DEL - 对
ZSET:用ZREMRANGEBYRANK 0 99循环删,比ZREM单个删更省 CPU
最关键的是:这类清理逻辑绝不能放在 HTTP 请求生命周期里执行,必须走异步任务(如 hyperf/task)或独立的 Command,并配速率限制(例如每秒最多处理 3 个大 Key)。











