
前阵子整理Redis笔记翻到“通用命令”这一节时突然发现很多人学了Redis的五大数据类型却把最基础的这一组命令给漏了。其实通用命令才是每天运维、排查、面试里出现频率最高的东西——不管你是刚接触Redis的新手还是已经写过不少业务代码的开发者KEY、EXISTS、TYPE、DEL、EXPIRE、TTL这些命令都属于“你不一定天天用但一旦用错就折腾半天”的类型。这篇文章就把这一组命令好好梳理一遍说说每个命令怎么用、适用场景在哪、有哪些容易踩的坑再结合缓存治理和日常排障的常见场景做几个实战演练争取让你看完就能直接上手。1. 通用命令到底是哪些——先搞清楚这个命令家族1.1 为什么要单独学通用命令Redis的数据类型命令String、Hash、List、Set、ZSet解决的是“怎么往某个结构里读写数据”的问题而通用命令解决的是“不管什么类型都绕不开的那批操作”。你可以把数据类型命令理解成仓库里不同货架的摆放规则通用命令则是仓库门口那套通用的进出货登记系统——无论你从哪个货架取货都得先过这道基础流程。这套命令之所以重要一方面是因为它们在日常运维中太常用了。比如上线了一个新功能往Redis里写了很多key隔天发现内存涨得厉害你想看一眼现在都有哪些key、各自是什么类型、有没有设置过期时间——这些操作全都落在通用命令的范畴里。另一方面面试的时候通用命令也是区分“背过文档”和“真用过Redis”的关键点。很多人知道SET和GET但一被问到“如何安全地遍历线上大量key”“TTL返回-1和-2分别代表什么”立刻就卡壳。这些细节都是通用命令能回答的。还有一个容易被忽略的点通用命令是很多高级特性的地基。分布式锁要用的SETNX、EXPIRE组合缓存治理里经常提到的过期时间设置、内存淘汰策略的前置判断以及AOF重写、RDB持久化之前的key扫描底层都离不开通用命令的支撑。把这一组命令吃透后面学分布式锁、缓存穿透治理、主从同步都会顺畅很多。1.2 通用命令清单概览Redis官方文档其实没有一份叫“通用命令”的严格分类但业界约定俗成把下面这些命令归为一组命令作用常用吗KEYS按模式查找key生产慎用测试常用SCAN游标式遍历key生产遍历首选EXISTS判断key是否存在高频TYPE查询key的数据类型高频DEL删除key高频UNLINK异步删除key生产推荐EXPIRE设置过期时间秒高频PEXPIRE设置过期时间毫秒低频TTL查看剩余过期时间秒高频PTTL查看剩余过期时间毫秒中频PERSIST移除过期时间中频RENAME重命名key低频COPY复制key低频OBJECT查看key内部结构低频DUMP / RESTORE序列化与反序列化低频RANDOMKEY随机返回一个key低频这张表里KEYS和SCAN是需要重点关注的因为它们的定位完全不同。KEYS是一次性扫描整个键空间命令简单但数据量大时会阻塞Redis所以生产环境不推荐SCAN是分批遍历每次返回少量key配合游标继续迭代对服务影响小是线上排查时的首选方案。2. 核心命令逐个拆解——从key到value的全链路操作2.1 增删改查EXISTS、TYPE、DEL、UNLINK先看最基础的判断类命令。EXISTS key [key ...]用来判断一个或多个key是否存在返回数字表示存在的key个数。这个命令在业务里很常见比如做缓存前置判断先看Redis里有没有目标数据没有再查数据库。但要注意Redis 3.0之后EXISTS才支持同时传多个key而且它是O(N)复杂度N是key的数量所以批量判断时别一次传几百个key会有一点耗时。TYPE key返回key对应值的类型返回值可能是string、list、hash、set、zset、stream等如果key不存在则返回none。这个命令在排障时特别有用。我遇到过一种情况开发同学反馈“明明设置了过期时间但数据还在”结果一查TYPE发现当初写入时用的是Hash类型但过期时间却设置在了一个不存在的String key上自然毫无效果。类似这种问题先跑一遍TYPE就能快速定位是数据结构选型的问题还是过期策略的问题。删除命令这边DEL key [key ...]是最传统的删除方式同步阻塞地释放内存。当删除的value特别大比如一个几百MB的String或者一个包含数十万元素的HashDEL在主线程里同步执行会卡住Redis一段时间。所以在生产环境Redis 4.0之后引入的UNLINK key [key ...]更推荐——它只是把key从键空间中摘除并把内存释放移到后台线程异步执行命令本身立即返回不会阻塞主线程。实践中如果只是删一两个小keyDEL和UNLINK差别不大如果涉及大key清理、批量删除一定要用UNLINK。2.2 生命周期管理EXPIRE、TTL、PERSIST过期时间这块是缓存治理的核心。EXPIRE key seconds设置key在N秒后过期PEXPIRE key milliseconds则精确到毫秒。TTL key返回剩余存活时间秒PTTL key返回毫秒精度值PERSIST key则移除过期时间让key永久存在。TTL的返回值有几个需要记牢key存在且设置了过期时间时返回大于等于0的数key存在但没有设置过期时间时返回-1key不存在时返回-2。很多人把-1和-2搞混其实只要记住“-1表示没有过期时间-2表示key都没了”就行。这个区分在排查内存泄漏时非常关键——如果一个本应该过期的key长期返回-1说明写入时压根没设置过期时间程序可能存在遗漏。设置过期时间有几个细节值得注意。第一SET key value EX seconds是一条原子命令它把写入和设置过期时间合并在一起而SET key value加EXPIRE key seconds是两条命令存在中间时间窗口。如果在那条EXPIRE执行之前进程崩溃key就变成永久key了所以实际开发中尽量用SET命令自带的EX参数或者SETEX命令保证原子性。第二对已存在的key再次调用EXPIRE会覆盖之前的过期时间少数场景下需要留意这个行为。第三PERSIST能够把过期key变成永久key但要注意它返回1表示成功0表示key不存在或本身没有过期时间。2.3 数据迁移与结构探查RENAME、COPY、OBJECT、DUMP/RESTORERENAME key newkey用于重命名key简单直接但有坑如果newkey已经存在RENAME会直接覆盖掉它。所以实操中若需要“不存在才改名”的语义就得用RENAMENX key newkeyNX即Not eXists返回1表示改名成功、0表示目标已存在。另外RENAME是原子的这个在需要保证一致性时要记住。COPY source destination [DB]是Redis 6.2引入的用于在同一实例或不同DB之间复制key不删除原key。DUMP key能序列化key的value得到一段二进制数据RESTORE key ttl serialized-value可以把它恢复成新的key。这两个命令配合起来可以在Redis实例之间迁移数据不过只是最原始的方式生产上通常还是用MIGRATE或者第三方工具保密性、增量同步都不如专业方案。OBJECT命令的常用子命令是OBJECT ENCODING key查看key的内部编码类型。比如String可能返回int或embstrHash在元素少时可能是ziplist元素多了会变成hashtable。这个在分析内存占用和类型转换时能派上用场。还有一个OBJECT IDLETIME key返回key自上次访问以来空闲的秒数配合内存淘汰策略可以找出长时间没被访问的key辅助冷热数据分析。3. 实操场景演练——缓存治理、安全遍历与排障3.1 场景一设计一套规范的缓存过期策略假设你在做一个用户信息缓存key是user:info:{id}value是用户资料的JSON串。很多新手会直接写成SET user:info:12345 {name:张三,level:5} EXPIRE user:info:12345 3600这样写虽然能跑但不是最优。一来两条命令非原子存在中间状态二来可读性和扩展性一般。更推荐的做法是SET user:info:12345 {name:张三,level:5} EX 3600加一个EX 3600就完事。这里面的逻辑是SET自带的EX参数保证写入和过期设置原子完成不会出现“key写入了但过期时间没设置上”的情况。如果业务上需要缓存和DB保持一致过期时间可以设置成“TTL阈值”配合主动更新比如TTL小于600秒时主动刷新缓存而不是等它自然过期减少缓存穿透的风险。还有一种常见需求是“查看集群中哪些key马上要过期”。你可以借助SCAN配合TTL实现。思路是用SCAN遍历所有key对每个key调用TTL当TTL返回小于某个阈值比如60秒时就认为这个key即将过期。这样就能在缓存雪崩之前提前发现一批即将过期的key。不过要注意SCAN遍历的是当前库的全部key如果数据量非常大这个操作也不能频繁跑建议做成定时任务低频执行。3.2 场景二用SCAN安全遍历大规模key严格来说SCAN已经超出“通用命令”的语义范围但它和KEYS的对比是绕不开的面试点。先说KEYS的命令形式KEYS * KEYS user:* KEYS user:info:*KEYS支持glob风格的通配符*匹配任意字符串?匹配单字符[]匹配字符集合。写起来确实方便但问题在于它是一次性全量扫描数据量大时会阻塞Redis主线程数秒甚至更久在线上环境无异于一颗定时炸弹。我以前在没经验的时候对着一个有800万key的实例跑过一次KEYS user:*直接导致那个实例的读写延迟飙升到好几秒从那以后我再也不敢在生产环境用KEYS了。SCAN的用法是SCAN cursor [MATCH pattern] [COUNT count]第一次调用传游标0返回两个结果一个是下一次要用的新游标另一个是本次扫描到的key列表。当新游标返回0时说明扫描完成。比如SCAN 0 MATCH user:* COUNT 100这里的COUNT 100不是返回100个key而是暗示本次遍历的槽位数量实际返回的key数量可能多于或少于100。SCAN每次只遍历一部分数据虽然多次命令之间key可能发生变化新增或删除但它能在遍历期间不阻塞整个实例。正因为这个特点线上排查key分布、批量清理数据都应该用SCAN而不是KEYS。3.3 场景三用通用命令排查线上问题排查Redis问题有一套比较成熟的命令组合。比如线上突然出现“缓存穿透”大量请求直接打到数据库第一步先看相关key还在不在EXISTS hot:news:1001 TTL hot:news:1001如果EXISTS返回0说明key已经没了如果TTL返回-1说明根本没设置过期时间就有可能是代码里漏加了过期时间导致缓存长期不失效进而引发穿透问题。再看key的类型是否匹配写入时的预期TYPE hot:news:1001如果预期是String结果返回list那就要怀疑是不是不同业务共用了同一个key前缀把数据结构写串了。这类问题靠EXISTS、TTL、TYPE三连就能定位个八九不离十。再比如线上内存告警想找出哪里来的大key。没有现成工具的话可以用SCAN结合DEBUG命令不过更简单的方式是先用RANDOMKEY抽样看看。RANDOMKEY随机返回一个key多跑几次就能粗略感知key的分布情况。当然要精确统计我建议用redis-cli --bigkeys它内部就是借助类型判断和长度估算来实现扫描的原理和我们手动用SCAN加OBJECT差不多但封装得更方便。4. 常见问题与排查技巧实录4.1 命令执行报错与权限问题速查实操中容易撞上的问题整理成一个速查表现象可能原因解决办法(error) ERR no such keykey不存在先确认key名和DB编号别选错库(error) WRONGTYPE Operation against a key holding the wrong kind of value类型用错用TYPE查看当前key类型理清写入链路(error) ERR value is not an integer or out of rangeEXPIRE数值类型不对检查seconds参数是否为数字有没有溢出TTL返回-1key没有过期时间回顾写入代码是否设置EXPIRETTL返回-2key不存在或已过期区分业务场景是数据丢失还是符合预期集群中执行KEYS报错集群模式不支持跨slot遍历改用SCAN或在每个节点单独执行这里重点说一下“选错DB”的问题。Redis默认有16个库0到15连接时有些人喜欢用SELECT 1切到别的库。如果线上用的库号不统一排查时可能导致“明明Redis里有数据但EXISTS找不到”。我的建议是新项目尽量只用DB 0把命名空间靠key前缀来区分这样既方便排查也方便集群化改造。选库的习惯会变成迁移时的暗坑极难排查。4.2 误删恢复、性能陷阱与日常防范先说误删。Redis没有像MySQL那样的undo logDEL、UNLINK、FLUSHALL这种操作一旦执行数据基本就没了。所以对大key、核心业务key删之前至少做三件事第一确认key的备份状态有没有触发过RDB或AOF第二用DUMP把序列化结果临时保存一份到本地文件第三如果是删一批key先把key列出发出来人工过目一遍再执行。DUMP/RESTORE这套操作虽然平时用途少关键时刻是救命的DUMP user:info:12345 RESTORE user:info:12345 0 serialized-dataRESTORE的第二个参数是过期时间传0表示永久不过期。这一段序列化数据可以直接写成文件后面恢复时再加载。当然DUMP的数据是二进制格式的不要尝试用文本编辑器直接读。再说性能陷阱。很多人为了清理过期key写了一个脚本循环调用KEYS *加DEL这是很危险的操作。正确做法是SCAN加UNLINK把删除放到后台异步执行。还有的人习惯在一个EXISTS里传几十上百个key这也会造成单次命令耗时上升。写代码时要注意批量操作的粒度单次调用控制在合理的范围内。还有一个经验之谈定期检查“没有过期时间却长期占据内存”的key。可以用SCAN配合OBJECT IDLETIME找出空闲时间很长的key这些是冷数据的典型特征。如果业务上确认这些数据不再使用可以直接清理或者重新设置过期时间。这算是一种轻量级的内存治理方法不需要额外引入工具一条SHELL脚本就能跑。4.3 面试高频追问——通用命令的底层细节很多技术面试官喜欢围绕这组命令做延伸提问下面这几个问题建议重点准备。第一个问题是“TTL返回-1和-2的区别”前面已经说过这一题考的是对key状态的理解。再深入一点面试官可能会问“如果key设置了过期时间但它一直没被访问Redis什么时候真正删除它”这里涉及Redis的过期键删除策略惰性删除和定期删除。惰性删除是当命令真正访问到某个过期key时才删定期删除是后台每100ms抽样删除一批过期key。通用命令的TTL、EXISTS查询本身也会触发惰性删除所以你会看到某些key还没到点就查不到了这是正常现象。第二个问题是“生产环境为什么禁用KEYS”除了阻塞主线程KEYS在大key扫描时还会占用大量内存和CPU尤其是在主从复制架构里主节点阻塞会直接导致从节点数据同步延迟。解决思路就是SCAN加MATCH。MATCH模式与KEYS一样支持glob但SCAN的MATCH模式有个特性它是在遍历过程中对返回的key做过滤而不是像KEYS那样直接全量匹配所以即使模式不太精确也不会额外增加太多负担。第三个问题是“UNLINK和DEL的差异”DEL同步释放内存、阻塞主线程UNLINK异步释放内存、不阻塞主线程。Redis 4.0之前只有DEL4.0之后加入UNLINK。需要注意的是UNLINK并不是对所有类型都是“完全异步”的如果value占用的内存很小它可能还是会在主线程里直接释放掉异步成本反而更高但对外表现一致所以你不用太纠结这个内部细节。5. 从通用命令延伸出去——这套知识还能怎么用通用命令的学习价值不只是“会敲几个命令”它能延伸到好几个方向。最典型的就是分布式锁。Redis分布式锁最常见的实现是SET key value NX EX seconds这里的NX和EX组合本质就是通用命令里原子性思维的延伸。理解了EXPIRE、DEL、SETNX这些基础能力再去理解Redisson的看门狗机制、锁续期逻辑就会顺理成章。另一个延伸方向是缓存治理。缓存穿透、缓存击穿、缓存雪崩最终落实到Redis端的操作无非就是判断key存在与否EXISTS、设置过期时间EXPIRE/SETEX、删除keyDEL/UNLINK、异步刷新可以结合TTL做阈值触发。通用命令就是这些治理策略的“底层执行单元”。再往工程化说很多Redis可视化客户端比如RedisInsight、Another Redis Desktop Manager它们左侧的key树展示、类型图标、TTL倒计时都是基于EXISTS、TYPE、TTL、SCAN这些命令来实现的。理解这些命令你不仅能手动排查问题也知道可视化工具背后到底发出了什么请求对定位“为什么这个客户端里能看到、那个客户端里看不到”这类问题会很有帮助。这一组命令学完之后建议自己开一个Redis实例造一些不同类型的key然后挨个跑一遍EXISTS、TYPE、TTL、SCAN、DUMP、RESTORE。不需要多少时间但对命令的感知会完全不一样。尤其是SCAN一定要亲手试几次观察游标的变化规律理解“为什么它不是一次性返回全部结果”。Redis的命令看似分散真正用起来你会发现它们之间环环相扣从通用命令开始是性价比最高的一条学习路径。