Redis通用命令实战:键管理、过期策略与运维排坑指南
发布时间:2026/10/9 6:51:14 锦皓数字建站

前面几篇把Redis的安装、配置和数据类型的框架大致过完了从这篇开始正式进入命令实操。按照系列规划我先讲通用命令——也就是不区分具体数据类型、作用在key层面或者服务器层面的一批命令。这批命令看着零碎但几乎所有线上问题排查、性能优化、日常运维都绕不开它们。把这批命令吃透后面学五大类型的专属命令会轻松很多因为你会发现很多操作逻辑是相通的。这篇文章我会把通用命令按使用场景重新组织而不是照着官方文档按字母序罗列。为什么要这么讲因为文档是按命令名称排序的但实战中你面对的是某个key状态异常线上误操作怎么办过期时间没生效这类具体问题脑子里得有一张问题到命令的映射图而不是命令到语法的对照表。学命令最忌讳死记硬背语法理解命令背后的设计意图遇到问题时自然知道该用哪条命令。1. 为什么要把通用命令单独拎出来讲1.1 通用命令在Redis知识体系里的位置Redis有五大基础数据类型还有Bitmaps、HyperLogLog、GEO这些扩展类型每种类型都有自己的操作命令。但无论你操作哪种类型都会先遇到一个前置问题这个key到底存不存在它是什么类型它还有多久过期要怎么把它删掉这些问题靠类型专属命令解决不了必须用到通用命令。如果拿仓库来类比类型专属命令是往货架上摆放和取用特定商品的规则而通用命令是管理整个仓库的规则——知道仓库里有哪些货物、货物什么时候过期、如何快速找到一批货物、如何清点数量。没有这套底层规则你了解再多商品摆放细节也没法真正管理好仓库。在官方文档里通用命令分布在各个功能模块下看着比较散。我按实际使用场景把它们重新分成四组分组核心命令典型场景连接与服务器PING、ECHO、SELECT、AUTH、INFO、CONFIG确认连接状态、切换库、查看服务信息键空间管理KEYS、SCAN、EXISTS、TYPE、DEL、UNLINK、RENAME查找、判断、删除、重命名key生命周期管理EXPIRE、PEXPIRE、TTL、PTTL、PERSIST设置和查看过期时间数据迁移与序列化DUMP、RESTORE、MOVE跨实例迁移数据、备份单个key1.2 先掌握通用命令的三个理由第一排查问题效率直接翻倍。线上Redis出问题第一步永远是确认key在不在、什么类型、多大、过期没这些全是通用命令。第二步才是看具体类型里的数据长什么样。我见过太多新手一上来就对着某个类型的命令瞎猜最后发现压根是key不存在或者类型不对。第二错误操作的代价极高。KEYS命令在数据量大时会把Redis阻塞住DEL命令删除超大集合也会卡顿这两个坑是生产环境最常见的通用命令事故。提前把通用命令的行为边界搞清楚比事后补救划算得多。第三通用命令是所有类型命令的公共底座。比如EXPIRE不只对String有效对List、Hash、Set、ZSet同样有效DEL可以删除任意类型的key。学习的时候把公共底座打牢后面学各类型命令时只需要关心类型专属的逻辑不需要反复回来看公共部分。2. 先聊那些不起眼的基础设施命令2.1 PING、ECHO、SELECT连接层的三板斧PING大概是Redis最简单的命令了它的作用就是确认客户端和服务器之间的连接还活着。连接正常时返回PONG执行效果如下127.0.0.1:6379 PING PONGPING还有带参数的形式参数会被原样返回这在某些连接池健康检查脚本里会用到。不过日常开发中更常用的是在执行真实命令之前用PING确认服务可用性。比如你的应用启动时如果有Redis连接自检逻辑底层调用的就是PING。ECHO命令则是把参数原样返回用来验证往返链路是否通畅实际使用频率很低知道有这么个东西就行。SELECT命令用于切换逻辑数据库。Redis默认有16个库编号0~15这个数量可以在redis.conf里通过databases参数修改。执行SELECT 3就切到了3号库后续所有操作都在这个库上进行。这里我得先泼一盆冷水多库功能在生产环境建议尽量少用原因我放到后面详细说。在单机学习阶段理解SELECT的用法就足够了。2.2 DBSIZE和AUTH统计与安全一个都不能少DBSIZE用于统计当前数据库中key的总数时间复杂度是O(1)执行很快。命令效果如下127.0.0.1:6379 SELECT 3 OK 127.0.0.1:6379 DBSIZE (integer) 42DBSIZE的值可以作为Redis健康度的参考指标之一。比如你可以在监控系统里配置一个告警如果某个业务的key数量在短时间内异常猛增很可能是代码里出现了无意义的重复写入或者是缓存穿透导致大量空值被写入。AUTH命令用于认证有两种使用场景。一种是在连接后执行AUTH password完成身份验证另一种是在redis.conf中配置requirepass之后Redis 6以上的版本支持ACL多用户权限管理命令形式更丰富。无论哪种形式核心思路都是防止Redis裸奔在公网环境里——这点必须强调Redis默认没有密码如果部署到公网且没有配置防火墙基本等于把数据直接暴露出去被勒索、被挖矿脚本盯上是大概率事件。2.3 关于SELECT多库的一个忠告有段时间我特别爱用多库把不同业务的数据分到不同库中觉得这样隔离得很干净。后来线上出过几次问题我才彻底改掉这个习惯。最大的问题是多库让定位问题变得困难。一个Redis实例里有16个库你得在排查时反复确认当前用的是哪个库。用了SELECT 2又用了SELECT 0命令一多很容易记混线上数据误删时连删的是哪个库都说不清。更麻烦的是Redis Cluster模式压根不支持多库只有db 0。也就是说如果你们团队以后要把单机Redis升级到集群模式所有基于多库的代码都要改一遍。与其到那时痛苦重构不如从一开始就只用db 0。那不同业务之间怎么隔离答案是部署多个Redis实例或者在key命名前缀上做区分比如user:10001和order:10001。现代的Redis运维实践普遍是一个实例一个业务多库已经逐渐被抛弃。这个认知我建议越早建立越好。3. 键空间管理真正决定线上稳定性的命令3.1 KEYS命令的原罪与SCAN的正确姿势KEYS命令用于按照模式匹配查找key例如KEYS user:*会返回所有以user:开头的key。学习的角度讲它很直观因为通配符匹配符合直觉。但生产环境里KEYS是个高危命令。原因在于KEYS的时间复杂度是O(N)N是整个数据库中key的总数。Redis是单线程模型一条命令在执行期间会阻塞其他所有命令。如果你的实例里有几百万个key执行一次KEYS user:*可能阻塞几百毫秒甚至几秒这对高QPS的业务是致命的大量请求会排队超时。正确的替代方案是SCAN命令。SCAN的特点是每次调用只返回一小部分key并且返回一个游标通过不断迭代游标来遍历所有key。关键区别在于SCAN的每次迭代执行时间很短不会长时间阻塞Redis。SCAN的基本用法如下127.0.0.1:6379 SCAN 0 COUNT 10 1) 153 2) 1) user:1 2) session:2 3) cache:10086 ...第一次调用时游标传0返回结果的第一个字段是下一次要用的游标。不断用返回的游标继续调用直到某次返回的游标为0表示遍历完成。COUNT参数表示一次迭代预计返回的key数量期望值不是精确值Redis底层会根据这个值调整遍历的槽位数。写一段Python示例演示完整的SCAN遍历并删除匹配key的过程import redis r redis.Redis(host127.0.0.1, port6379, db0) cursor 0 deleted 0 while True: cursor, keys r.scan(cursorcursor, matchsession:*, count500) if keys: # 批量异步删除避免阻塞Redis r.unlink(*keys) deleted len(keys) if cursor 0: break print(f共删除 {deleted} 个key)这里有两个容易被忽视的细节。第一个SCAN的MATCH模式匹配是在数据返回之后才过滤的不是服务端扫描时直接按模式筛选。也就是说即使匹配率极低每次迭代消耗的CPU和遍历成本是一样的。第二个SCAN在遍历过程中如果key有新增或删除可能出现重复返回或极少数遗漏这是弱一致性的设计为了换取不阻塞的性能。所以SCAN更适合用于批量清扫过期数据这类对准确性要求不高的场景。3.2 EXISTS、TYPE、DEL、UNLINK组合出优雅的键管理流程EXISTS命令判断key是否存在返回1或0时间复杂度O(1)。Redis 3.0.3之后的版本支持一次传入多个key返回存在的key数量127.0.0.1:6379 EXISTS user:10086 (integer) 1 127.0.0.1:6379 EXISTS user:10086 user:10087 user:10088 (integer) 2TYPE命令查看key的类型返回string、list、hash、set、zset等127.0.0.1:6379 TYPE user:10086 stringTYPE命令在排查问题时的价值非常高。常见场景是代码里存了个String类型但取出来解析失败一查发现key被其他逻辑写成了Hash类型——这就是典型的key命名冲突问题通过TYPE可以快速定位。DEL命令删除一个或多个key127.0.0.1:6379 DEL user:10086 user:10087 (integer) 2DEL的时间复杂度是O(N)N是被删除key的数量。注意如果删除的key是List、Hash、Set或ZSet且元素数量巨大DEL本身也可能阻塞Redis因为Redis需要释放这个key占用的所有内存。这就是UNLINK命令存在的意义。UNLINKRedis 4.0引入的作用是先把这个key从键空间中摘除再在后台线程异步释放内存。前台不会卡顿后台慢慢清理。所以如果是删除大对象无条件选UNLINK删除小key两者差别不大。我在实际工程中统一用UNLINK省得每次判断对象大小。脚本清扫场景下UNLINK还有一个好处即使同时删除几万个key也不会产生明显的阻塞尖刺。3.3 RENAME、RENAMENX别小看重命名的应用场景RENAME命令用于修改key的名字127.0.0.1:6379 RENAME user:10086 user:10086-backup OKRENAME有两个细节需要记住。第一如果目标key已经存在RENAME会直接覆盖目标key相当于先把目标删掉再重命名这可能导致数据丢失。第二RENAME的时间复杂度是O(1)。RENAMENX命令在目标不存在时才执行重命名否则返回0。这在某些要么整个生效、要么不生效的场景下很有用。比如灰度发布时你想把新配置的key名生效但不想影响已经存在的旧key就可以用RENAMENX保证不会覆盖。讲一个我实际遇到的场景运营后台做活动配置配置数据存Redis。上线新活动时需要切换key名但切换过程不能丢失正在进行的活动数据。用RENAMENX new_config old_config如果old_config已经存在就放弃替换避免直接覆盖导致活动数据错乱。4. 生命周期与过期时间缓存系统的命脉4.1 EXPIRE家族与TTL返回值详解EXPIRE命令给key设置过期时间单位是秒PEXPIRE单位是毫秒。命令效果如下127.0.0.1:6379 SET login_token abc123 OK 127.0.0.1:6379 EXPIRE login_token 3600 (integer) 1 127.0.0.1:6379 TTL login_token (integer) 3597TTL命令查看key的剩余过期时间返回的是秒数。PTTL返回毫秒。TTL的返回值需要重点关注有三种情况TTL返回值含义大于0key存在剩余过期时间-1key存在但未设置过期时间永久key-2key不存在我见过不少新手看到-1理解为还有1秒过期这是完全错误的。-2表示key已经不存在了也是很多缓存穿透问题的根源之一。记住这两个返回值的区别能帮你快速读懂线上状态。PERSIST命令移除key的过期时间让它变成永久key127.0.0.1:6379 PERSIST login_token (integer) 14.2 过期策略两个懒惰机制的组合理解过期策略有助于你想清楚为什么有些key过期后不会立刻被删除。Redis的过期删除策略是惰性删除加定期删除的组合。惰性删除是指当客户端访问一个key时Redis会先检查它是否已过期如果过期就删除并返回空结果。这种策略的好处是CPU开销最小——只有被访问的key才会做过期检查坏处是如果过期key一直没被访问它会一直占着内存。定期删除则是Redis后台每100ms随机抽取一部分设置了过期时间的key检查是否过期并删除。如果抽样中发现过期key比例超过四分之一就继续抽直到比例降到合理范围。这个策略保证即使大量key过期后无人访问内存也能被逐步回收。为什么不用定时器逐个精确删除因为精确删除需要维护每个key的定时器内存开销巨大。惰性加定期是一种工程权衡既不产生大量CPU空转又能控制内存占用。理解了这一点你就明白key过期后立刻消失的想法是不现实的总有一个延迟窗口。4.3 缓存雪崩的实战预防过期时间里的数学设置过期时间时最怕的是大量key在同一时刻过期导致流量瞬间全部打到数据库这就是缓存雪崩。预防的核心手段之一是把过期时间分散开。假设业务上有5000个业务数据需要缓存如果全部设置3600秒过期那每小时都会有一个瞬间的DB压力尖峰。更稳妥的做法是给过期时间加上随机偏移import random import redis r redis.Redis(host127.0.0.1, port6379, db0) base_ttl 3600 for item in items: # 基础过期时间加上0到300秒的随机偏移 ttl base_ttl random.randint(0, 300) r.setex(fcache:{item[id]}, ttl, item[data])这样5000个key的过期时间分布在3600到3900秒之间数据库不会在同一秒被打满。这个偏移量不是随便取的要根据集群的QPS估算。QPS越高偏移区间可以越大削峰效果越明显。还有一个细节虽然SET命令可以直接用SET key value EX 3600设置过期时间但SETEX命令在语义上更清晰代码可读性更好。另外在事务或Pipeline里批量设置各自的TTL时注意不同命令之间不要在同一个key上冲突。5. 序列化与迁移DUMP、RESTORE的进阶玩法5.1 为什么要单独做序列化命令很多人觉得Redis的序列化就是把对象转成字符串存进去用GET取出来再反序列化这是应用层的逻辑。但DUMP和RESTORE是另一层概念——它们是Redis内部的数据序列化格式能保持key的内部编码和值的完整信息。前者的场景是自己定义对象的序列化方式JSON、ProtoBuf等Redis只负责存储字符串。后者是把Redis中一个key的完整数据以二进制形式导出再原封不动地恢复到另一个实例中不关心值内部是什么数据类型。通俗点说GET/SET是让你读盒子里的东西而DUMP/RESTORE是让你把整个盒子搬走。这在单key数据迁移的场景中非常有用。5.2 DUMP与RESTORE实际操作DUMP命令接收一个key返回它的序列化值127.0.0.1:6379 SET user:10086 {name:张三,level:3} OK 127.0.0.1:6379 DUMP user:10086 \x00\x1c{\name\:\\xe5\xbc\xa0\xe4\xb8\x89\,\level\:3}\x0e\x00\xd8\xc4|q\xb6l\xb8拿到的这个二进制字符串你需要在另一个实例上通过RESTORE还原。RESTORE的参数是目标key名、过期时间毫秒0表示永不过期、序列化值# 在另一台Redis实例上执行 127.0.0.1:6380 RESTORE user:10086 0 \x00\x1c{\name\:\\xe5\xbc\xa0\xe4\xb8\x89\,\level\:3}\x0e\x00\xd8\xc4|q\xb6l\xb8 OK 127.0.0.1:6380 TYPE user:10086 string 127.0.0.1:6380 GET user:10086 {\name\:\\xe5\xbc\xa0\xe4\xb8\x89\,\level\:3}RESTORE如果遇到目标key已存在会报BUSYKEY错误。如果想直接覆盖可以加REPLACE参数RESTORE user:10086 0 序列化值 REPLACE。这个能力用于临时把单个key从测试环境复制到生产环境临时排查或者做配置热迁移非常实用。5.3 版本兼容性的坑DUMP的格式和Redis版本强相关官方并不保证跨大版本完全兼容。比如在Redis 5.x上DUMP出来的数据在Redis 7.x上可能无法RESTORE成功。所以跨大版本迁移时不要直接依赖DUMP/RESTORE而是用redis-cli --pipe做全量迁移或者用专门的迁移工具。我在一次Redis 6.2升级到7.0的演练中就遇到过RESTORE报错的情况。当时没注意版本差异用生产环境的DUMP数据在测试环境RESTORE直接返回ERR DUMP payload version or checksum are wrong。排查半天才确认是版本兼容问题。顺带提一句DUMP的序列化值里包含CRC64校验和所以任何一位数据被改动RESTORE都会报错。这个设计保证了数据完整性但也意味着你不能手动修改序列化值中的任何信息。如果要对迁移的数据做加工必须反序列化成业务对象后再写入。6. 运维视角排查问题时的命令组合拳6.1 用INFO和CONFIG快速定位问题INFO命令查看Redis服务器的各种统计信息支持按模块分段查看。比较常用的有127.0.0.1:6379 INFO memory # Memory used_memory:1048576 used_memory_human:1.00M used_memory_rss:3096576 ...INFO memory看内存占用INFO commandstats看各命令执行次数和耗时INFO clients看连接数INFO keyspace看各个库的key数量和过期key数量。问题排查第一步就靠这些数据缩小范围。CONFIG GET命令用于读取运行时配置CONFIG SET用于热修改部分配置。比如临时修改maxmemory限制可以用CONFIG SET maxmemory 2gb。需要注意的是CONFIG SET修改的配置在重启后会恢复为redis.conf中的值如果想把修改持久化需要手动执行CONFIG REWRITE。6.2 线上踩坑实录误用KEYS导致的阻塞说一个我印象深刻的线上事故。某个业务模块的团队在排查一个找不到key的问题时直接在线上执行了KEYS user:*。当时那个实例总共四百多万个key这条命令执行了约1.8秒。Redis的单线程模型瞬间被阻塞期间所有读写请求全部排队下游接口大面积超时。当时排查的思路值得学习先查INFO commandstats发现KEYS命令的total_time_ms值异常偏高结合慢日志确认了罪魁祸首。随后用UNLINK配合SCAN脚本清理了误操作产生的连锁垃圾数据。这件事之后我在团队定了一条规矩生产环境任何情况下不允许直接执行KEYS统一用SCAN替代。监控系统里也加了针对KEYS命令的告警规则一旦检测到线上执行KEYS立刻告警。另外一个思路是使用SLOWLOG命令查慢日志。SLOWLOG GET 10可以查看最近10条执行时间超过阈值的命令。排查性能问题时这个命令能直接告诉你哪些命令在拖慢Redis127.0.0.1:6379 SLOWLOG GET 3 1) 1) (integer) 12 2) (integer) 1588323123 3) (integer) 1857233 4) 1) KEYS 2) user:*6.3 通用命令高频问题速查表问题原因排查命令正确处理key删除后字段还在代码里被读到key被DEL后有短暂间隙业务重试逻辑读到了空EXISTS、TTL确保删除后调用方处理空值不能只靠Redis层大量key过期导致内存不降惰性删除定期删除的延迟窗口INFO memory、INFO keyspace等待定期清理或主动SCAN触发访问加速过期回收批量删除耗时严重用了DEL且key是大对象DBSIZE、SLOWLOG改用UNLINKTTL总是返回-1key未设置过期时间TTL检查写入逻辑是否漏了EXPIRESCAN遍历的结果和DBSIZE对不上遍历期间key有增删DBSIZE、SCAN理解弱一致性的特性不要把它当精确统计工具DUMP数据RESTORE失败版本不兼容或数据被篡改DUMP、RESTORE确认两实例大版本一致不做手动数据修改个人经验来说通用命令的很多坑不是命令本身多难而是使用时没有意识到Redis单线程模型的约束。KEYS、DEL这种高危命令在小型测试环境里没什么感觉一旦到了百万key的线上环境代价立刻放大。所以我的建议是从第一天学习就养成用SCAN替代KEYS、用UNLINK替代DEL的习惯等你真正上了生产环境这个习惯能帮你避开至少一半的Redis事故。下一篇会进入String类型详解那是五大类型里最常用也最容易被用错的一个我们下一篇见。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。