大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
redis-cli 实战:学会「看」Redis
这一课你将能做到
- 熟练使用 redis-cli 完成键的增删改查:
SET/GET/DEL/EXISTS/TYPE/RENAME/TTL,并准确解释每个返回值(OK/(nil)/(integer))的含义 - 用
SCAN游标方式遍历键空间,用自己的话说清为什么生产环境禁用KEYS *(单线程阻塞) - 按「项目:模块:实体:id」规范设计 key,能用
MEMORY USAGE查看单个 key 的内存占用,并说出本站三类 key 前缀的分工
为什么要学
上一课你已经能 SET/GET。但真实项目里你面对的不是三五个 key,而是成千上万个。本站生产 Redis 里至少有三类 key 并存:
satoken:login:token:xxxx——每个在线用户一条rl:203.0.113.7——每个被限流统计的 IP 一条coderoadmap:tracks:list——业务缓存
不会用命令行查看、筛选、统计这些 key,你就是在蒙眼操作:排查「缓存为什么没生效」,你需要 EXISTS 和 TYPE 确认 key 状态;排查「Redis 内存怎么涨了」,你需要 MEMORY USAGE 定位大 key;批量清理一类数据,你需要 SCAN——而用错 KEYS * 可能把线上 Redis 卡死几秒甚至几十秒,所有接口跟着超时。
redis-cli 是 Redis 的手术刀。本 track 后面每一课的验证步骤都依赖它,这一课把它练熟,后面才不卡壳。
先建立图像
Redis 的键空间可以想成一栋大楼的储物柜,每个柜门贴着唯一标签(key):
db0(默认库)
├─ satoken:login:token:8f3a... → 柜内有值,类型 string,TTL 86400s
├─ rl:203.0.113.7 → string 计数器,TTL 60s
├─ coderoadmap:tracks:list → string(JSON),TTL 300s
└─ user:1001:bookmarks → set
对单个 key 的操作都是「按柜门标签定位」:GET 开柜看货、TYPE 问材质、TTL 看电子标签的倒计时、DEL 整柜拆掉、EXISTS 只看柜子在不在。
而 KEYS 和 SCAN 是两种「巡楼」方式:
KEYS * → 一次性把所有柜门标签抄到一张纸上再返回(巡楼期间整栋楼封锁)
SCAN 0 → 从 0 号门开始,每次只抄一小段,返回「下次从哪继续」的游标(不封锁)
人话:key 像文件路径,冒号像文件夹分隔符。乱起名等于所有文件扔在桌面上,删错就哭。
跟着做
步骤 1:确认 Redis 在跑并进入交互模式
docker start redis-dev 2>/dev/null; docker exec -it redis-dev redis-cli
(用的是第 1 课创建的容器;如果没创建过,先执行 docker run -d --name redis-dev -p 6379:6379 redis:7-alpine。Windows 本机没装 redis-cli 也没关系,docker exec 用容器内的即可。)
步骤 2:键的增删改查基础六连
依次执行,注意观察每个返回值:
SET user:1001:name "xiaolin"
GET user:1001:name
EXISTS user:1001:name
TYPE user:1001:name
RENAME user:1001:name user:1001:nickname
GET user:1001:nickname
DEL user:1001:nickname
GET user:1001:nickname
预期输出依次是 OK、"xiaolin"、(integer) 1、string、OK、"xiaolin"、(integer) 1、(nil)。
要点:EXISTS 返回 1/0;DEL 返回实际删掉的个数(删不存在的 key 返回 0、不报错);(nil) 表示 key 不存在,不是错误。
步骤 3:体验 KEYS 和 SCAN 的区别
先在容器里批量造 20 个演示 key:
docker exec redis-dev sh -c 'for i in $(seq 1 20); do redis-cli SET "book:$i" "v$i" > /dev/null; done'
回到交互模式,对比两种遍历:
KEYS book:*
SCAN 0 MATCH book:* COUNT 5
KEYS 一次返回全部;SCAN 返回「一小批 key + 一个新游标」。把输出里的游标数字填回 SCAN <游标> MATCH book:* COUNT 5 继续扫,直到游标回到 0 为止。亲手走完这个循环,SCAN 才算真的会了。
步骤 4:--raw 解决中文显示
SET greet "你好,Redis"
GET greet
exit
docker exec -it redis-dev redis-cli --raw
GET greet
默认模式下中文显示成 \xe4\xbd\xa0\xe5\xa5\xbd 这样的 UTF-8 字节转义(看起来像乱码,但数据没坏);--raw 按原始字节输出,终端就能正常显示中文。日常排查中文内容一律加 --raw。
步骤 5:MEMORY USAGE 与键空间统计
docker exec redis-dev sh -c 'redis-cli SET big:string "$(yes a | head -n 10000 | tr -d "\n")"'
docker exec -it redis-dev redis-cli
MEMORY USAGE big:string
DBSIZE
MEMORY USAGE 返回该 key 占用的字节数(含元数据开销,10000 字符的字符串大约返回 12000 左右);DBSIZE 返回当前库的 key 总数。生产排查「内存去哪了」就靠这两个命令按前缀逐类摸底。
步骤 6:对照本站的 key 命名规范
本站 Redis 里三类前缀、三种责任:
satoken:login:token:... ← Sa-Token 框架内部命名(框架决定,别手工改)
rl:{clientKey} ← RedisRequestRateLimiter 里的 "rl:" + key
coderoadmap:tracks:list ← 业务缓存(综合实战课你会亲手写)
命名规范:项目:模块:实体:id,全小写,冒号分层,不用空格、不超长。好处有三个:SCAN MATCH 能按前缀批量定位;一眼看出数据归属;清理脚本永远不会误删别的模块。
讲清楚要点
- KEYS 是 O(N) 全量遍历,执行期间阻塞整个 Redis:命令执行是单线程,百万级 key 的
KEYS *可能卡几秒,期间所有请求排队。生产禁用(运维甚至会在配置里rename-command KEYS ""直接废掉它)。 - SCAN 是分批游标遍历,不阻塞:每次只扫一小批(
COUNT只是提示值,返回数量可能多于或少于它),返回新游标,游标回到 0 表示遍历结束。遍历期间 key 有增删时结果「不精确但可用」,对缓存清理场景完全够。 - 返回值就是协议:
(nil)= 不存在,(integer) 1= 存在/成功个数,OK= 成功。看返回值调试,比猜快得多。 - --raw 是给人看中文和给脚本用的;默认转义模式是给协议调试用的,两者不是数据坏了没坏的区别。
- RENAME 是原子操作:可用于无停机换 key;注意 RENAME 到已存在的 key 会直接覆盖目标。
- 命名规范是防事故的第一道墙:
satoken:/rl:/coderoadmap:三类前缀分立,保证清理脚本永不误删登录态。
你可能会懵的地方
-
KEYS 在开发机跑一下也不行吗? 开发机 key 少,跑一下没事。危险的是把「开发机随手 KEYS」的习惯带进生产——肌肉记忆要用 SCAN 练出来。
-
SCAN 的 COUNT 5 是精确返回 5 个吗? 不是。它是「这一批大概扫多少个桶」的提示,实际返回数量可能多也可能少。判断遍历是否结束只看游标是否回到 0,不看数量。
-
为什么有 db0~db15 十六个库?能不能当多租户用? 历史遗留设计。官方明确不建议:库无法按业务命名、集群模式下只有 db0。业务隔离靠 key 前缀,不靠库编号——本站只用 db0。
-
TYPE 都有哪几种返回值?
string/list/set/zset/hash/stream。本站业务缓存全是 string 存 JSON,其他类型出现在框架(Sa-Token)或你主动选用高级结构时(第 4 课)。 -
EXISTS 调多了会不会慢?
EXISTS k1 k2 k3一次传多个 key 是 O(N) 但纯内存查找,微秒级。真正要警惕的是KEYS、SMEMBERS大集合这类「返回大数据」的命令,阻塞和流量都来自它们。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
生产环境敲 KEYS * | 单线程阻塞,全站请求排队 | SCAN 0 MATCH xxx COUNT 100 游标遍历 |
key 命名随手起(t1、temp_list2) | 无法批量定位,误删难排查 | coderoadmap:track:{slug} 分层命名 |
| 中文显示乱码以为是数据坏了 | 只是客户端转义显示 | redis-cli --raw 重连 |
用 GET 确认存在再 DEL | 两次往返且有竞态 | 直接 DEL,看返回个数 |
| 用 db1~db15 做业务隔离 | 官方不建议,集群不支持 | key 前缀隔离,固定 db0 |
随手 DEL satoken:*「清理」 | 把真实用户踢下线 | 只删自己前缀,satoken 只读 |
卡点急救
NOAUTH Authentication required:连的实例设了密码。交互模式里先AUTH 你的密码,或连接时用redis-cli --askpass(交互输入,避免明文进 shell 历史)。- 中文显示成
\xe4\xbd\xa0:默认协议转义模式,exit后用redis-cli --raw重进。 WRONGTYPE Operation against a key holding the wrong kind of value:对 string key 执行了 list 命令之类。先TYPE key看类型再选命令;要换类型先DEL。- SCAN 一直停不下来:终止条件是游标返回 0。每次把输出里的新游标填回 SCAN,别每次都从 0 开始。
Connection refused:容器没起或端口没映射。docker ps确认0.0.0.0:6379->6379/tcp在列;不在就docker logs redis-dev看报错。
面试常问
-
为什么生产禁用 KEYS?SCAN 好在哪? KEYS 一次性 O(N) 遍历,Redis 命令单线程执行,期间所有请求被阻塞;SCAN 分批 + 游标,每批开销小、不阻塞。代价是遍历期间结果不保证精确(可能重复或漏),缓存清理这类场景可以接受。
-
你们项目的 Redis key 是怎么设计的? 三类前缀:
satoken:(Sa-Token 框架会话)、rl:(限流计数,RedisRequestRateLimiter 拼接)、coderoadmap:(业务缓存,如coderoadmap:tracks:list)。统一规范是项目:模块:实体:id、全小写冒号分层,便于 SCAN MATCH 批量定位、防止跨模块误删。 -
怎么定位 Redis 的大 key? 单个 key 用
MEMORY USAGE;全库排查用redis-cli --bigkeys(内部就是 SCAN,不阻塞),或者离线分析 RDB 文件的 rdb-tools。
自测三问
DEL一个不存在的 key 返回什么?EXISTS呢?TTL对「存在但永不过期」的 key 返回什么?- SCAN 的终止条件是什么?COUNT 参数是精确值还是提示值?
- 本站三类 key 前缀分别是什么?各自存什么数据?为什么必须分立?
小结一句话
redis-cli 的核心是「按 key 精确操作 + SCAN 游标遍历」:生产永远用 SCAN 代替 KEYS,用分层前缀把键空间管理成可维护的目录树。
延伸
- redis-cli 官方文档
- SCAN 命令
- 下一课:String 与计数器——SET 的 NX/EX 选项、INCR 原子计数,以及本站限流器 RedisRequestRateLimiter 的逐行源码解读。