适用读者:要拿 Redis 干"正经事"的人。
前置:S12-J04。
实测环境:Redis 7.0.15(2026-11-05)。
覆盖旧文:873(90) 871(85) 878(75) 501(36) 2674(95) 1541(25) = 406 点。
覆盖扩展:E-S12-014(延迟队列)、E-S12-015(SCAN 游标)。
上一篇:S12-J04 · 事务、Lua 与 Pipeline
下一篇:S12-A01 · 集群与脑裂。
1. 一句话结论
Redis 的"高级用法"都是用基础结构拼出来的:ZSet 当延迟队列、INCR+EXPIRE 做限流、SCAN 替代 KEYS、pub/sub 做广播(但不保证送达)。
2. 延迟队列:ZSet + score(实测)
ZADD dq <到期时间戳> "job1"
ZRANGEBYSCORE dq -inf <当前时间戳> LIMIT 0 10
实测:
到期前取出: [] ← score 还没到
到期后取出: [job1] ← 到点后可见
| 优点 | 缺点 |
|---|---|
| 简单、精确到秒 | 不保证执行(需消费者轮询) |
| 支持任意延迟 | 无 ACK/重试(要自己实现) |
判据:"消息丢了要紧吗?" 要紧 → 用 MQ(S17);不要紧/量小 → ZSet 够用。
3. 限流:固定窗口与滑动窗口
| 方案 | 写法 | 缺陷 |
|---|---|---|
| 固定窗口 | INCR key + EXPIRE |
窗口边界可双倍放行 |
| 滑动窗口 | ZSet 存时间戳 | 内存占用高 |
| 令牌桶 | Lua 脚本 | 实现稍复杂(推荐) |
固定窗口的标准写法:
INCR rate:uid:202611051930
EXPIRE rate:uid:202611051930 60
"为什么要 Lua":INCR 与 EXPIRE 要原子(否则 INCR 后崩溃 → key 永不过期)。
4. KEYS vs SCAN(实测)
| 命令 | 20 万 key 耗时 | 阻塞 |
|---|---|---|
KEYS * |
0.28 s | ✅ 全局阻塞 |
SCAN 0 COUNT 100 |
0.01 s | ❌ 不阻塞 |
SCAN 的三个保证/不保证:
| 保证 | 不保证 |
|---|---|
| 遍历期间存在的 key 都会被返回 | 可能重复返回 |
| 不阻塞 | 不保证"同一时刻快照" |
判据:"需要一次性拿到全部 key 吗?" 不需要(能增量处理)→ SCAN;需要 → 换数据结构(如用 Set 维护索引)。
5. 发布订阅的边界(A-871)
| 特性 | pub/sub |
|---|---|
| 送达保证 | ❌ 不保证(订阅者掉线就丢) |
| 持久化 | ❌ |
| 多实例 | ❌(Cluster 下需额外处理) |
判据:"消息丢了会怎样?" 会出问题 → 用 Stream(5.0+,有消费者组与 ACK)。
6. 统计 1 亿 key 的思路(A-2674)
| 方案 | 说明 |
|---|---|
SCAN + 本地聚合 |
简单,但慢 |
| HyperLogLog | 基数估计(误差 ~0.81%),内存极小 |
| Bitmap | 适用于"是否出现过" |
| 抽样 | 近似统计 |
判据:"要精确值还是趋势?" 趋势 → HLL/Bitmap;精确 → 别用 Redis 做全量统计。
7. 五个误区
| 误区 | 真相 |
|---|---|
| "ZSet 延迟队列可靠" | 无 ACK/重试 |
"INCR+EXPIRE 没问题" |
非原子,可能永不淘汰 |
"SCAN 是快照" |
可能重复、不保证一致 |
| "pub/sub 能替代 MQ" | 不保证送达 |
| "Redis 能做精确全量统计" | 内存与阻塞都不允许 |
8. 自检
- ZSet 延迟队列怎么用?缺陷是什么?
- 固定窗口限流的原子性问题?
KEYS与SCAN的实测差距?- pub/sub 与 Stream 的关键差别?
- 统计海量 key 的三个方案?
9. 下一篇
S12-A01 · 集群:主从、哨兵、Cluster 与脑裂
用法讲完,进架构师层:复制为什么丢数据、脑裂怎么防。
本篇属于系列《S12 · Redis 与缓存》。旧文来源:A-873/871/878/501/2674/1541;扩展点 E-S12-014/015。延迟队列与 SCAN/KEYS 为 Redis 7.0.15 实测。