入门 Redis 的时候,最容易被忽略掉的就是通用命令这块。很多人背了半天五种数据类型的增删改查,真到线上排查问题的时候才发现,用的最多的居然是 EXISTS、TTL、SCAN、INFO 这类“不挑数据类型”的命令。缓存大面积失效、连接数打满、大 Key 删不掉、主从切换后命令超时……每一个现场靠的都是通用命令来定位和止血。
这篇内容就围绕 Redis 通用命令展开,我会先从“通用命令到底是怎么定义的”讲起,再把 Key 生命周期、批量扫描、过期策略、运维管理命令分批拆开来讲。文中包含大量我在实际环境里验证过的做法、踩过的坑和面试时经常被追问的细节,适合正在系统学习 Redis 的开发者,也适合想规范线上操作方式、避免误删误清的运维同学。
1. 先把“通用”两个字拆开理解
1.1 通用命令和数据命令的分界线
很多人会把“通用命令”理解成“所有命令里比较常用的一部分”。其实不是的,Redis 官方文档里有一个明确的大类划分,叫 Generic Commands,这一类的共同点是:它们操作的是 Key,而不是 Key 底下存储的数据结构。也就是说,不管 Key 背后是字符串、哈希、列表还是集合,这些命令都能用。
举个例子说明区别。
HSET user:1001 name "Jack" 这条命令只能用在哈希类型上,如果你对一个字符串类型的 Key 执行 HSET,Redis 会直接报错 WRONGTYPE。而 EXISTS user:1001、DEL user:1001、TTL user:1001 这类命令完全不关心 Key 内部存的是什么,它们只关心 Key 本身存在不存在、什么时候过期、要不要删除。这就像你在文件夹上点右键删目录,系统不会关心目录里放的是图片还是文档——这是对文件条目本身的操作,不是对文件内容的操作。
通用命令大体可以分成几组:
- 连接与实例级命令:
PING、ECHO、SELECT、SWAPDB、AUTH、INFO、CONFIG、CLIENT - Key 生命周期命令:
EXISTS、TYPE、DEL、UNLINK、EXPIRE、TTL、PERSIST、RENAME - 批量遍历命令:
KEYS、SCAN、RANDOMKEY - 数据库级操作命令:
DBSIZE、FLUSHDB、FLUSHALL
理解了这个分界线,你自然也就明白了一个很容易被忽略的问题:FLUSHALL 删的是所有 Key,DEL 删的是单个 Key,它们都属于通用操作,执行之前必须极度谨慎。
1.2 十秒上手:最常用的几个通用命令
如果你之前没有系统整理过,这里先给出一组“上来就能用”的命令。
bash复制# 检查 Redis 是否存活,返回 PONG 就代表连接正常
redis-cli -p 6379 PING
# 输出一段自定义字符串,常用来测试连接和延迟
redis-cli ECHO "hello"
# 查看当前数据库里一共有多少个 Key
redis-cli DBSIZE
# 查看某个 Key 是否存在
redis-cli EXISTS user:1001
# 查看某个 Key 是什么数据类型
redis-cli TYPE user:1001
# 查看某个 Key 的剩余过期时间,单位是秒
redis-cli TTL user:1001
# 切换数据库,默认有 16 个库,下标从 0 到 15
redis-cli -n 1 SELECT 1
这里提醒一句:ECHO 看着简单,在排查“客户端配置对不对、密码通不通、端口通不通”的时候非常管用。比如你用客户端连接池连不上,可以先在服务器本机执行 redis-cli -a 密码 PING,如果本机能 PONG、远程不能,那就说明问题出在网络或者安全组方向,而不是 Redis 配置本身。
DBSIZE 这个命令也要学会用。很多人平时不关心实例里有多少 Key,等到内存暴涨或者慢查询增多的时候再去排查,往往为时已晚。养成每天定时记录 DBSIZE 的习惯,能让你很早就捕捉到 Key 数量的异常增长。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Key 对象的生命周期管理
2.1 DEL、UNLINK 和异步删除机制
删除 Key 是最常见的操作,但里面门道很深。早期版本只有 DEL,这是个同步命令。Redis 是单线程执行命令的,DEL 会同步释放这个 Key 对应的内存。如果这个 Key 很小,比如只是个几百字节的字符串,那没有任何感知。但如果它是一个包含几百万元素的哈希、大列表或者大集合,DEL 在释放内存时可能让 Redis 卡顿几十毫秒甚至更久。
我在线上遇到过这样一个案例:有个团队用 Redis 存用户会话,其中一个 Key 的值是一个包含 300 多万个小字段的哈希,相当于 2.5GB 内存。清理会话时有人习惯性执行了 DEL,结果主线程直接卡了接近 6 秒。这 6 秒里所有读写请求全部排队,接口 P99 直接打满,上游服务陆续超时报错。事后分析慢日志才发现是这条同步删除把自己压垮了。
Redis 4.0 之后引入了 UNLINK,语义上就是“异步删除”。执行 UNLINK key 时,主线程只是把 Key 从键空间里摘掉,把真正的内存释放动作交给后台线程慢慢做,所以命令本身返回非常快,不会阻塞主流程。
bash复制DEL user:session:1001
UNLINK user:session:1001
如果你的 Redis 版本在 4.0 以上,删除大 Key 或无把握大小的 Key 时,我建议一律优先考虑 UNLINK。两者的返回结果是一样的,成功删除了返回 1,Key 不存在返回 0。唯一需要留个心眼的是,异步删除意味着内存不会立刻降下去,后台回收需要一点时间,别删除完立刻盯着 used_memory 看,那是会吓到自己的。
2.2 EXISTS、TYPE 和 RENAME 的细节
EXISTS 和 TYPE 都属于“只读探测”型命令,平时不怎么显眼,排查问题时作用很大。
bash复制EXISTS key1 key2 key3
Redis 3.0.3 之后 EXISTS 支持一次传多个 Key,返回的是“存在的 Key 数量”,不是布尔值。比如你有 5 个 Key,其中一个不存在,返回就是 4。有同学常在这里犯错,拿返回值去跟 1 做比较,逻辑上就岔了:多个 Key 时它返回的是计数,单个 Key 时才兼容“0 或 1”的用法。
TYPE 返回的数据类型名称包括:
string:字符串list:列表set:集合hash:哈希zset:有序集合stream:流none:Key 不存在
这里有个经验:判断 Key 是否过期时,不能只依赖 EXISTS。如果 Key 已经过期,EXISTS 会返回 0,TYPE 会返回 none,这没问题。但如果你要区分“过期了”和“被删除了”,还得配 TTL 来看,因为 TTL 对两种情况的返回值是不一样的,后面我会专门讲。
RENAME 在线上使用时要格外小心,因为它会“静默覆盖”。假设你执行 RENAME old_key new_key,而 new_key 本来就存在,那么 new_key 原来的值会被直接替换掉,不会给你任何警告。所以我一般在具有覆盖风险的场景里用 RENAMENX,它的语义是“只有目标 Key 不存在时才重命名成功”。
bash复制RENAME old_key new_key
RENAMENX old_key new_key
顺带一提,很多语言的客户端封装里没有直接暴露 RENAMENX,用 Lua 脚本封装替代也常见。重命名操作虽然不常用,但在做数据迁移、Key 名规范整改时几乎是刚需。
2.3 用 TTL 和 PERSIST 控制 Key 的存活时间
TTL 返回值的含义是很多人面试时翻车的地方。
bash复制TTL key
- 返回大于 0 的整数:剩余存活秒数
- 返回
-1:Key 存在,但没设置过期时间,意味着永久有效 - 返回
-2:Key 不存在,或者已经过期被清理掉了
这里要特别注意 -1 和 -2 天差地别。排查问题的时候,如果 TTL 返回 -1,说明 Key 还在,只是没有设置超时时间;如果返回 -2,那 Key 已经没了,就不要继续对着这个 Key 做什么写操作了。
PERSIST 的作用是去掉 Key 的过期时间,让它变成永久有效。有些业务场景里,临时 Key 在活动结束后需要保留下来转成长期数据,就可以用这个命令:
bash复制PERSIST temp_key
还有个很常用的操作是“续期”。比如某个分布式锁需要续期,你可以直接重新执行 EXPIRE 来重置剩余时间。但要注意:EXPIRE 不能和 SET 的过期逻辑混着理解。如果先 SET key value EX 100,然后执行 SET key value2(不带 EX),Key 的过期时间会被移除。也就是说,普通 SET 会清掉已经设置的 TTL,这一点很多人在开发联调时发现“明明设了过期,为什么一直没消失”,排查半天最后发现是后续有一次 SET 把 TTL 覆盖了。
3. 线上查询 Key 的两种方案:KEYS 与 SCAN 的取舍
3.1 KEYS 为什么被“嫌弃”
KEYS 命令可以按模式匹配查看键名,例如:
bash复制KEYS user:*
KEYS product:???
它的实现是遍历整个键空间。如果实例里面有几十万甚至上百万个 Key,一次 KEYS * 会阻塞 Redis 主线程,把所有请求全部踩停。这里面的本质原因还是 Redis 单线程模型:遍历操作得不到 CPU 时间片,其他命令就得排队。
很多人以为“我就是查一下有几个 Key”,没关系。但在生产环境,量大之后这就是事故源头。我见过一个真实场景:有开发同学为了方便,在管理后台写了个“查看全部缓存 Key”的功能,底层就是 KEYS *。结果当天下午有 800 多万个 Key 的时候,接口直接卡了十几秒,整个实例的监控曲线变成一条直线,线上核心接口全部超时。
所以 Redis 官方文档里也特别注明:生产环境下 KEYS 仅供调试使用,禁止在业务代码里直接调用。它并没有说“一定不能用”,而是要求你先评估实例规模。如果只是为了看几个 Key,开头加个 LIMIT 是不行的,KEYS 根本没有游标或限制参数,它必须完整遍历,这是硬伤。
3.2 SCAN 游标式的正确打开方式
SCAN 是解决上述问题的替代方案。它的核心思路是用游标(cursor)分批次扫描,每次拿到一小部分结果,不阻塞整体服务。
bash复制SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
第一个调用的游标传 0,返回的游标如果还是 0,代表整个遍历结束。下面是一段典型调用:
bash复制redis-cli SCAN 0 MATCH user:* COUNT 100
这里有几个容易误解的点。
第一,COUNT 不是“每次返回 100 个元素”,而是“每次遍历的哈希桶数量”。Redis 的 Key 是存在哈希表里的,每次 SCAN 会按桶为单位扫描。默认 COUNT 是 10,如果你把 COUNT 调大到 1000,单次返回的数量会明显变多,但并不意味着总是能返回 1000 个匹配的 Key。因为 MATCH 过滤是在桶扫描完成之后做的,实际匹配数量可能远少于 COUNT。
第二,SCAN 不保证键在遍历过程中完全一致。它在遍历期间,如果有一些 Key 被新增或删除,可能会出现跳过、重复返回的情况。所以客户端一定要对返回结果做去重处理,不能理所当然地认为“遍历完这一遍,所有 Key 都刚好各来一次”。
第三,SCAN 可以指定 TYPE,例如只扫描 string 类型或者 hash 类型。这个选项在某些场景下很好用,比如想排查是否有某个特定类型的超大 Key 残留,就不用先把所有 Key 取回来再逐个 TYPE 判断了。
bash复制redis-cli SCAN 0 TYPE hash COUNT 100
3.3 实际场景:如何安全地批量删除前缀 Key
很多业务都有“按前缀清除缓存”的需求,例如活动结束后清理所有 activity:2024:* 的 Key。正确做法是用 SCAN + 异步删除,而不是 KEYS + DEL。
我常用的方式是在控制台或者脚本里分批处理,以 bash 为例:
bash复制redis-cli --scan --pattern "activity:2024:*" --count 1000 | xargs -L 200 redis-cli UNLINK
这个命令看起来简单,不过要注意几个问题。
--scan底层就是 SCAN,所以不会阻塞主线程,比 KEYS 安全。xargs -L 200表示每 200 个 Key 发起一次 UNLINK 调用,避免单条命令参数过长。- 如果数据量非常大,管道传参不一定适合所有环境,更稳妥的办法是自己在语言里用循环来写。
以 Java 为例,使用 Jedis 或者 Lettuce 的时候,更标准的写法是循环调用 SCAN,把每一批拿到的 Key 用 UNLINK 或者 Pipeline 方式删除。Pipeline 在这里是很推荐的,因为可以在一次网络往返里批量执行多条 UNLINK,减少 RTT 开销。
另一个细节是:如果 SCAN 返回结果里有些 Key 在遍历过程中刚好过期了,UNLINK 对它们返回 0 也完全正常,程序里直接忽略就好,不用当成异常去告警。
4. 过期键的处理机制与常见误区
4.1 过期设置的误区:秒、毫秒与绝对时间
EXPIRE 命令大家应该都很熟了,但很多人不知道它其实有 NX、XX、GT、LT 这些条件选项。这些选项是 Redis 7.0 加进来的,用的场景非常具体。
bash复制EXPIRE key seconds [NX | XX | GT | LT]
- NX:仅当 Key 当前没有过期时间时才设置。
- XX:仅当 Key 当前已经有过期时间时才设置。
- GT:仅当新设置的过期时间比当前剩余时间更晚时才覆盖。
- LT:仅当新设置的过期时间比当前剩余时间更早时才覆盖。
举个例子,如果一个 Key 当前 TTL 是 30 秒,你执行 EXPIRE key 100 GT,因为 100 秒比 30 秒晚,所以会设置成功,TTL 变成 100 秒。如果你执行 EXPIRE key 10 GT,因为 10 比 30 早,设置会被忽略,TTL 还是 30。
除了 EXPIRE,Redis 还提供:
PEXPIRE key milliseconds:以毫秒为单位设置相对过期时间EXPIREAT key unix-time-seconds:设置一个绝对过期时刻PEXPIREAT key unix-time-milliseconds:以毫秒精度设置绝对过期时刻
日常开发里用得最多的是 EXPIRE,但做精细化缓存治理时,EXPIREAT 也很有用。比如你想让一批 Key 统一到第二天的凌晨 3 点过期,完全可以计算一个固定时间戳然后批量设置,每条命令的过期逻辑就完全一致,不用逐个设置相对于当前时刻的秒数。
4.2 过期键是怎么被清理的
Redis 对过期 Key 的清理策略是“惰性删除 + 定期删除”的组合,并不是“到点就立即删”。
惰性删除的意思是:每次访问 Key 时,先检查它是否已经过期,如果过期,就直接删除并返回 Key 不存在。这套逻辑保证了大部分过期 Key 不会给你读到脏数据。
定期删除的意思是:Redis 每隔一段时间(默认每秒执行 10 次左右)会随机抽取一批带有过期时间的 Key,检查它们是否到期,到期就删除。这个策略存在的意义是避免那些“长期没人访问的过期 Key”一直占着内存。但注意它是抽样,不是全量扫描,所以总会有少量过期 Key 残留在内存里,直到被访问或再次被抽查到。
理解这一点就能解释很多现象。比如你明明给 Key 设置了 30 秒过期,但 1 分钟后在 DBSIZE 里还能看到它,这不一定是没设置上,而是 Redis 还没来得及去清理。此时通过 TTL 看它,如果返回的是 -2,那就说明已经被惰性删除了;如果返回的是剩余秒数,那就是彻底清理走了。
另一种情况是内存压力很高时,即使某些 Key 没到过期时间,也可能被 Redis 的淘汰策略提前清理。maxmemory 和 maxmemory-policy 配置决定了这个行为。过期策略和内存淘汰策略是两条独立的链路,别再混为一谈了。
4.3 排查 TTL 异常的几条经验
我排查过不少 TTL 相关的线上问题,这里把高频踩坑点列出来。
第一,别在主从架构里依赖从节点的 TTL 做精确判断。虽然 Redis 官方实现会保证主从最终一致,但从节点收到 DEL 命令或者惰性删除的时间有细微延迟,所以刷监控时看到个别从节点 TTL 比主节点大几秒是正常的,不要紧张。
第二,如果一个 Key 本来有 TTL,但在过期时间之前被重新赋了值,TTL 会被清除。这是 Redis 的基本行为,不属于 Bug。需要保留过期时间的更新场景,建议统一使用带 EX 选项的赋值命令,例如 SET key value EX 300。
第三,大促前经常要做“缓存预热 + 过期时间打散”。如果所有 Key 都设置了整半小时或整点的过期时间,很容易造成同一时间点大量 Key 同时失效,然后流量穿透到数据库。常见的做法是在基础过期时间上加上随机偏移,比如 300 秒到 600 秒之间的随机数。
5. 管理命令用得好,故障时间少一半
5.1 FLUSHALL / FLUSHDB 的“安全阀”
FLUSHDB 清空当前数据库,FLUSHALL 清空全部数据库。这两条命令一旦执行,在你没有备份的情况下,数据就是真的没了。所以它的风险等级和删库是一个档次的。
Redis 4.0 之后给这两个命令增加了 ASYNC 选项:
bash复制FLUSHDB ASYNC
FLUSHALL ASYNC
异步清空不会阻塞主线程,适合在数据库已经出现异常、必须快速清场的情况下使用。但我还是想多说一句:生产环境应该做好权限隔离,运维账号可以执行,业务账号尽量不给这个权限。最好的“安全阀”不是靠手速,而是靠权限管理和操作前备份。
如果你想做一个更安全的清空操作,可以在执行 FLUSHALL 前先手动触发一次 BGSAVE,把数据落盘保存下来,确认 RDB 文件生成成功后再清空,给自己留一条后悔路。
5.2 CLIENT 命令与连接治理
遇到“连接数被打满”“客户端大量 TIME_WAIT”之类的问题时,CLIENT 系列命令就是你的工具箱。
bash复制# 列出所有客户端连接
CLIENT LIST
# 查看当前连接数
INFO clients
# 杀掉指定客户端连接
CLIENT KILL ID 12345
# 给连接设置名字,方便日志排查
CLIENT SETNAME my-service
CLIENT LIST 的输出里能看到每个连接的 id、地址、名称、数据库编号、最后操作时间等字段。在线上一旦发现某个客户端连接数异常暴涨,可以用 CLIENT LIST 按地址维度统计,找出是哪个服务把连接池灌爆了。
CLIENT KILL 支持多种过滤方式。比较常用的是:
bash复制CLIENT KILL ID 12345
CLIENT KILL ADDR 10.0.0.1:6379
CLIENT KILL TYPE normal
注意低版本 Redis 的 CLIENT KILL 语法不太一样,6.2 之后才支持 ID 这种写法。使用前最好确认下版本,别把线上正在服务的连接全杀光了。
还有个命令在维护时很管用:CLIENT PAUSE。
bash复制CLIENT PAUSE 3000
执行后 Redis 会暂停处理客户端命令 3 秒,这期间所有命令会在队列里等待。做版本升级或者安全组切换时,先执行一下 CLIENT PAUSE 让写入流量短暂停顿,能有效避免数据竞争和连接中断风暴。
5.3 MONITOR、SLOWLOG 和 INFO 的配合使用
MONITOR 命令可以把 Redis 当前处理的每条命令都实时打印出来。听着很强大,实际生产环境千万别长时间开着,它会严重拖累 Redis 吞吐,单位时间内输出的日志量非常大。我一般只会在测试环境或者定位某个诡异调用链时,短暂开启几秒钟,抓完日志立刻关掉。
SLOWLOG 才是生产环境里更可靠的慢查询分析工具。默认慢查询阈值是 10 毫秒,也就是执行时间超过 10ms 的命令会被记录下来。
bash复制# 查看最近 10 条慢日志
SLOWLOG GET 10
# 查看慢日志总数
SLOWLOG LEN
# 清空慢日志
SLOWLOG RESET
结合 INFO commandstats,还能看到每个命令的调用次数、总耗时、平均耗时。我排查性能问题时,一定会先看 INFO commandstats 里有没有某个命令高频而且超时占比极高。找到可疑命令后,再看 SLOWLOG GET 的具体样本,基本就能锁定是慢查询、大 Key 还是命令本身的阻塞问题。
INFO 的分段输出也非常适合做巡检脚本的指标采集:
INFO server:版本、进程号、运行时长INFO clients:连接数INFO memory:内存使用情况INFO persistence:RDB / AOF 状态INFO stats:每秒请求数、命中率、过期键数量INFO replication:主从复制状态INFO keyspace:每个数据库的 Key 数量和过期 Key 数量
我曾经在排查一次缓存命中率下降时,就是通过 INFO stats 里的 expired_keys 发现每分钟都有大量 Key 在同一秒过期,进而定位到代码里把过期时间写成了固定值而不是随机值。一条监控指标,比翻半天代码都提速。
6. 把这些命令串成一套自己的巡检习惯
学了命令不等于会排查问题。如果你问我实际工作中最有用的是什么,我觉得是“把命令组合成固定动作”。
每天我至少会做的事:
bash复制# 存活检查
redis-cli PING
# 基础状态
redis-cli INFO server
redis-cli INFO memory
redis-cli INFO stats
# Key 数量与过期统计
redis-cli DBSIZE
redis-cli INFO keyspace
# 慢查询检查
redis-cli SLOWLOG GET 20
如果有告警,再看连接、主从和命中率,定位链路就非常清楚。这套动作用得多了,你会发现所谓的“Redis 翻车现场”,绝大多数都可以归因到几个通用问题上:Key 无限增长、过期时间集中、del 大 Key 阻塞、连接数异常、慢查询未治理。
把自己常用的命令写成一个小的巡检脚本,或者通过定时任务采集监控指标,比临时抱佛脚翻命令手册靠谱得多。我个人实测这样坚持两周,就已经能对实例的健康度形成比较敏锐的判断。以后再遇到线上问题,你的第一反应就不会是“这是什么命令”,而是“先看哪个指标、再跑哪条命令、怎么止血”,这个过程积累出来的经验,才是通用命令之外真正值钱的东西。
