很多人刚开始接触 Redis问的第一句往往是“基础常用命令有哪些”。说实话这种问题最难回答因为 Redis 命令本身并不复杂官方文档列出来也就 200 来个日常高频用到的最多二三十个。但为什么还是有那么多人觉得 Redis 命令难记、容易用错我这些年用 redis-cli 和各类客户端调 Redis 的经验是问题从来不在“记不记得住命令”而在于你有没有把命令和它背后的数据结构、使用场景、坑点对起来。这篇文章我不会给你贴一份干巴巴的命令大全而是按我实际使用 Redis 的思考路径把最常用、最容易踩坑的那部分命令讲透。每个命令都会配一个真实业务场景告诉你为什么这里该用这个命令什么情况下用了会出事。适合刚接触 Redis 的初学者也适合用了半年一年 Redis 但某些细节始终没搞明白的同学。1. 理解命令的前提Redis 命令其实是“键空间 数据类型”的二维组合很多教程一上来就列命令我觉得这是最大的误区。你如果不懂 Redis 存储模型命令在你眼里就是一堆乱码记了也会忘。Redis 本身是一个键值对数据库所有数据都存放在一个巨大的“键空间”里。你可以把它理解成一个大柜子每个格子里放了一个 keykey 对应的 value 才是你真正存的数据。那么问题来了value 可以是什么形态这就引出了 Redis 的五大基础数据结构String字符串、Hash哈希、List列表、Set集合、ZSet有序集合。每一个命令本质上做的是三件事中的一件操作键空间本身比如 DEL、EXISTS、TTL、KEYS针对某种数据类型操作 value比如 String 的 SET/GETList 的 LPUSH/LPOP订阅/发布、事务、脚本等特殊命令。所以你在学习命令时心里始终要有一张“二维表”横轴是数据类型纵轴是操作意图。拿 String 举例操作意图是“写”那就 SET是“读”那就 GET是“计数”那就 INCR。拿 Hash 举例“写一个字段”是 HSET“读一个字段”是 HGET“自增一个数字字段”是 HINCRBY。这样学命令你不需要死记硬背。你只需要记住Redis 一共有 5 种数据类型每种类型都有增删改查、批量操作、过期设置这几类命令。剩下的细节用的时候查一下就行用多了自然就记住了。1.1 先搞清楚键的规则在深入每个命令之前有一个基础中的基础必须强调Redis 的 key 是二进制安全的字符串。什么意思就是说 key 可以是任意二进制序列包括图片内容、序列化对象只要不超过 512MB 的上限。但实际开发中没人这么干大家约定俗成地用可读字符串。我强烈建议项目里形成一套 key 命名规范比如业务名:实体名:ID:字段名像user:profile:1234:name。这么做的好处是线上排查时一眼能看懂这个 key 是干嘛的用SCAN按模式匹配时能精准定位避免不同业务间 key 撞车。我在多个团队里都推过这套规范规则就一句话所有 key 必须有业务前缀前缀之间用冒号分隔禁止裸 key。1.2 选对数据类型比选对命令更重要命令用错通常不是语法问题而是数据类型选错。我见过有人把用户购物车直接序列化成 JSON 塞进 String每次更新都要先 GET、反序列化、改、再序列化、再 SET代码繁琐不说并发更新还会互相覆盖。换成 Hash 之后每个商品 ID 是一个 field数量是 value加一辆车就是HINCRBY cart:1234 sku_888 -1一次原子操作搞定。所以我的建议是拿到一个业务场景先别想命令先想数据形态。是单个值就选 String是对象就选 Hash是队列/时间线就选 List是去重集合就选 Set是需要排序的集合就选 ZSet。选好类型命令是水到渠成的事。2. 五大数据类型核心命令每个命令都对应一个真实业务场景这一章是全文的干货主体。我会按数据类型逐一拆解每个类型挑出真正高频的命令并说明使用场景和注意点。命令清单用表格列出方便你收藏后快速翻阅。2.1 String 类型不止 get/set 那么简单String 是 Redis 最基础的类型能存字符串、数字、二进制数据甚至序列化后的对象。但它的常用命令里有几个是很多新手不知道的宝藏。命令作用典型场景SET / GET写入/读取单个键值缓存单个结果、存储配置项MSET / MGET批量写入/读取多个键值一次查询多个配置项减少 RTTSETNX键不存在时设置Set if Not eXists分布式锁、防止重复执行任务SETEX / PSETEX设置值的同时指定过期时间秒/毫秒存储验证码、临时凭证INCR / DECR / INCRBY原子自增/自减/按步长自增计数器、限流、秒杀库存扣减APPEND在字符串末尾追加内容日志累积、消息体追加STRLEN获取字符串长度校验内容长度、控制单 key 大小这里重点说两个。第一个是SET key value EX seconds NX这种组合写法。很多人不知道 SET 命令可以同时设置过期时间和“不存在才写入”的条件总觉得要分两步走或者要用SETNX再单独EXPIRE。但分开写有个致命问题两步不是原子的中间进程崩溃就会留下一个永不过期的锁。正确写法是# 设置 key 为 value10 秒过期且仅当 key 不存在时生效 SET order:12345 paid NX EX 10这个写法在分布式锁场景几乎是标配后面第 6 章我会专门展开。第二个是INCR系列。很多人把它当成简单的“加一”没意识到它是原子操作。在并发场景下GET 计算 SET会丢更新但INCR本身在 Redis 服务端就是原子的多个客户端同时调用也不会互相覆盖。我做过一个活动页浏览量统计就是用INCRBY page:home:pv 1实现的代码量几乎为零性能远好于先 GET 再 SET 的方案。还有一个实用技巧用SETEX存短信验证码天然自带过期时间省得专门开一个定时任务去清理过期验证码。像“验证码 5 分钟有效”这种需求一行命令就解决。2.2 Hash 类型对象存储的最优解Hash 在 Redis 里是一个 string 类型的 field 和 value 的映射表。我把它理解成“对象里的字段集合”特别适合存结构化对象。命令作用典型场景HSET / HGET设置/获取某个字段的值更新用户昵称、读取用户手机号HMSET / HMGET批量设置/获取多个字段初始化用户资料、一次取多个字段HGETALL获取所有字段和值后端一次性加载整个对象HDEL删除一个或多个字段移除用户某个扩展属性HEXISTS判断字段是否存在校验某属性是否已初始化HINCRBY对字段值做原子自增购物车加数量、积分累加HKEYS / HVALS获取所有字段名/所有字段值导出对象属性列表HLEN获取字段数量统计对象维度数HSCAN游标式遍历字段大 Hash 对象的安全遍历Hash 最大的优势是字段级操作。同样是一个用户对象如果存成 JSON 字符串塞进 String你想改其中一个字段就得把整个字符串读出来、反序列化、修改、再序列化、写回每次都传输一整份数据。换成 Hash更新昵称就是HSET user:profile:1001 nickname 新昵称只传输一个字段的数据量还省了序列化开销。这在对象频繁局部更新的场景下收益非常明显。不过 Hash 也有它的禁忌HGETALL 慎用。如果一个 Hash 里有成千上万个字段HGETALL 会一次性把所有数据都拉出来内存、网络、反序列化全部被打满。我踩过这个坑一个商品聚合信息的 Hash 存了 5 万多个字段线上一个 HGETALL 直接把 Redis 的 CPU 打到 90%客户端全部超时。后来改成 HSCAN 分批遍历或者按业务维度拆分成多个小 Hash 才缓过来。2.3 List 类型队列和时间线的天然实现List 是一个双向链表头尾插入删除都是 O(1) 复杂度。凡是有“先后顺序”的场景List 都值得考虑。命令作用典型场景LPUSH / RPUSH从左侧/右侧插入元素消息列表、任务队列LPOP / RPOP从左侧/右侧弹出元素消费任务、取最新数据LRANGE获取指定范围的元素分页查询时间线LLEN获取列表长度显示队列积压数量LINDEX按下标获取元素随机访问队列指定位置LTRIM只保留指定范围内的元素限制列表长度、内存保护BLPOP / BRPOP阻塞式弹出元素可靠的消息队列消费端RPOPLPUSH弹出并推入另一个列表消息确认机制、循环队列List 最经典的应用场景是最新消息列表。比如一个知识社区的信息流用户发帖后用LPUSH把帖子 ID 推到 feed:1001 这个 key 下面然后分页时LRANGE feed:1001 0 9一页十条性能非常好。这里有个细节倒序用 LPUSH LRANGE顺序用 RPUSH LRANGE取决于你想要的排序方向。除了消息列表List 还能当轻量消息队列用。生产端LPUSH task:queue task_data消费端死循环里BRPOP task:queue 0其中 0 表示阻塞时间无限。BRPOP 比 RPOP 好的地方在于队列为空时不会立刻返回 null 空转消耗 CPU而是阻塞等待有新数据进来立刻返回。而且阻塞式命令天然带有“挂起等待”语义不用你手工 sleep 轮询。这里必须提醒一个容易踩的坑LTRIM 是控制 List 内存的重要命令。时间线类场景如果只 LPUSH 不 LTRIMlist 会无限增长撑爆内存。正确姿势是每次推送完顺手做一次裁剪LPUSH feed:1001 post:8888 LTRIM feed:1001 0 99这两条命令组合就把 feed 限制在最新 100 条超出部分自动丢弃。如果你做的是“只保留最近 N 条动态”的业务这个组合几乎是标准答案。2.4 Set 类型去重与交集运算一把好手Set 是无序字符串集合自动去重并且支持集合间的交、并、差运算。用一句话概括一切“元素是否在其中”“多个集合之间有什么关系”的场景都是 Set 的领地。命令作用典型场景SADD / SREM添加/移除元素添加标签、取消点赞SMEMBERS获取全部元素获取某个对象的全量关联集合SISMEMBER判断元素是否存在校验用户是否已点赞/是否在白名单SCARD获取元素数量统计访客人数、点赞数SPOP随机移除一个元素抽奖、随机发牌SRANDMEMBER随机获取元素但不移除随机推荐、抽奖展示SINTER / SUNION / SDIFF交集/并集/差集运算共同好友、推荐关注、权限差集举一个常见例子社交场景中的共同关注。用户 A 关注的博主 ID 集合存在follow:1001用户 B 的存在follow:1002想看共同关注一行命令SINTER follow:1001 follow:1002结果直接就是共同关注列表业务代码一行都不用写。如果放到关系型数据库里这得写一条多表关联 SQL可能还要把两个大集合拉到应用层手动比对。用 Set 的集合运算执行效率由 Redis 端保证。Set 还有一个经常被忽略的命令SRANDMEMBER和SPOP的区别。前者只随机返回元素不删后者会从集合中移除。抽奖场景如果你希望奖品不能重复发给同一个人就用 SPOP保证抽到的人被移出候选池如果你只是要展示几个随机样本比如“猜你喜欢”的候选商品就用 SRANDMEMBER不破坏原集合。很多人在抽奖需求上选错命令导致同一批用户被反复抽中或者候选池被意外清空其实就是这两条命令没分清楚。2.5 ZSet 类型排行榜的唯一正解ZSet有序集合是 Redis 里唯一支持按分值自动排序的数据结构。每个元素关联一个 double 类型的 scoreRedis 会按 score 从小到大维护顺序。所有跟“排名”“Top N”“按权重取数”相关的需求第一个想到的应该是它。命令作用典型场景ZADD添加元素并设置或更新 score写入榜单分数ZSCORE获取元素的 score查看某用户当前分数ZINCRBY原子增加某个元素的 score积分动态变更ZRANGE按分数从小到大获取元素查看排行榜正序列表ZREVRANGE按分数从大到小获取元素查看排行榜倒序列表Top NZRANGEBYSCORE按分数区间获取元素按分值段筛选ZRANK / ZREVRANK获取元素排名正/倒查看我的名次ZREM移除元素撤销某个上榜对象ZCARD获取元素数量榜单总人数ZCOUNT统计分数区间内元素数量分段统计热门场景是排行榜。假设一个积分排行榜用户每次获得积分调用一次ZINCRBY rank:total 10 user:12345查询 Top 10ZREVRANGE rank:total 0 9 WITHSCORES查询某用户当前排名ZREVRANK rank:total user:12345三个命令排行榜功能闭环连索引都不用建。ZSet 还有一个高阶玩法延时队列。把任务执行时间戳作为 score任务 ID 作为 member然后消费者定期执行ZRANGEBYSCORE delay:queue -inf 当前时间戳 LIMIT 0 10把到期任务取出来执行。虽然它没有专业的消息队列那么多特性但轻量级延时任务用 ZSet 完全够用我写过不少内部定时任务都是这么做的。这里有一个隐藏的细节ZRANGE key 0 -1如果集合很大同样会把全量数据拉到客户端。处理方式和 Hash 一样要么使用ZRANGE key 0 99分页要么用ZSCAN游标式遍历。凡是“全量获取”的命令在数据量上去之后都是危险命令这个直觉你得建立起来。3. 通用命令与过期策略键空间操作里最容易翻车的几个点数据类型命令是“怎么操作 value”通用命令是“怎么操作 key 本身”。这些命令跟数据类型无关但正是它们决定了 Redis 的可运维性。新手往往只关注 SET/GET忽略了这块结果一到线上排障就抓瞎。3.1 判断存在、删除、改名EXISTS / DEL / UNLINK / RENAME命令作用注意事项EXISTS判断 key 是否存在支持一次传多个 key返回存在的个数TYPE查看 key 的数据类型排查类型不匹配错误时很有用DEL删除 key同步删除大 key 会阻塞UNLINK异步删除 key对超大 key 友好先解除链接后后台回收RENAME改名如果新 key 已存在会覆盖谨慎使用RANDOMKEY随机返回一个 key调试用线上慎用这里我想重点说UNLINK。我见过一次事故同事用 DEL 删一个包含几百万元素的 Set结果 Redis 主线程直接阻塞了将近一秒所有读写命令全部排队线上出现大面积超时。原因很简单DEL 是同步命令删除大 key 时要把整个数据结构的内存都释放掉这个过程会卡住主线程。UNLINK就友善得多它先把 key 从键空间解除关联然后后台线程异步回收内存主线程不会被卡住。千万条规则总结成一句话不确定 key 多大时删除一律用 UNLINK不要用 DEL。3.2 过期时间管理EXPIRE / TTL / PERSISTRedis 的过期机制是很多新手搞不懂的重灾区。其实命令非常直观命令作用典型场景EXPIRE设置过期时间秒缓存 1 小时后失效PEXPIRE设置过期时间毫秒高精度临时数据TTL查看剩余生存时间监控 key 是否快过期PERSIST移除过期时间让 key 永久保留EXPIREAT设置到某个时间戳过期指定时刻失效TTL 返回值的含义很多人搞不清楚。大于 0 表示剩余秒数-1 表示永久不过期-2 表示 key 不存在。线上排查缓存是否生效时第一件事就是TTL key看返回 -1 就该警惕是不是忘记设过期时间了。这里我得插一个实战经验给缓存设置过期时间要加一个随机偏移量。比如所有 key 都设 3600 秒那么同一时刻写入的 key 会在同一时刻集体失效。此时如果大量请求同时打到数据库就是教科书级的缓存雪崩。标准做法是SETEX cache:hot:1001 data 3600 EXPIRE cache:hot:1001 3600 # 但如果刚才没设才需要单独调用实际业务代码里过期时间建议设置为基础时长加上一个 0 到 300 秒的随机值。这样 key 的失效时间被打散数据库的压力就被削峰了。这是缓存治理里最便宜也最有效的一个操作成本几乎为零。3.3 键的迭代为什么 KEYS 会被 1 秒打挂KEYS pattern是新手最喜欢用的命令因为可以按模式匹配到所有 key比如KEYS user:*。但它在生产环境是几乎禁止的社区里流传着“用 KEYS 会卡死 Redis”的说法这并不夸张。KEYS 的问题在于它要全量扫描键空间扫描过程中主线程不能干别的。如果这个 Redis 实例里 key 数量过百万一次 KEYS 就可能阻塞几百毫秒甚至更久线上读写全部遭殃。我在线上见过一次因 KEYS 命令导致的大规模故障排查半天最后发现是一条告警脚本里误用了 KEYS。替代方案是SCAN游标式增量遍历每次返回一部分 key 和一个游标通过反复调用完成全量遍历整个过程不会长时间阻塞主线程。用法# 第一次调用cursor 为 0 SCAN 0 MATCH user:* COUNT 100 # 返回下一个游标直到游标回到 0 表示遍历完成 SCAN 12345 MATCH user:* COUNT 100COUNT 参数是每次扫描的桶数参考值不是返回条数上限。实际使用中如果你需要删除一批匹配的 key正确姿势是 SCAN UNLINK 组合边扫边删而不是先 KEYS 全量查出所有 key 再循环 DEL。后者等于自己把自己打进事故现场。4. 事务、发布订阅与 Lua在基础命令上长出高级能力这一章的内容严格说也属于“基础命令”范畴但它们是很多初级使用者容易忽略的。理解了这一章你就能用 Redis 做更多“多一点”的事情。4.1 MULTI / EXEC为什么说 Redis 事务和你想的不一样Redis 的事务是三条命令的组合MULTI 开启事务把多个命令入队EXEC 一次性执行。看起来像关系型数据库的事务但实际上它的原子性很“特殊”。MULTI SET account:1001 balance 100 SET account:1002 balance 200 EXECRedis 事务的特点是在 EXEC 之前命令只是排队不会执行EXEC 时按顺序一次性执行完所有命令中途某个命令失败不会回滚之前的命令。这就意味着它没有关系型数据库的“要么全做要么全不做”语义。官方叫它“事务”其实更接近“批量打包执行”。所以我的经验是不要把业务逻辑的强一致性寄托在 Redis 事务上。它最大的意义在于减少网络往返把多条命令打包一把发给 Redis性能更好。如果确实需要多条命令组合的原子性优先考虑 Lua 脚本我们马上讲到。4.2 PUBLISH / SUBSCRIBE轻量级消息通知Redis 的发布订阅模型很简单一个客户端PUBLISH channel message其他订阅了 channel 的客户端实时收到消息。# 订阅端 SUBSCRIBE order:notify # 发布端 PUBLISH order:notify 新订单 10001 已创建它的优势是极轻量没有消息持久化、没有消费确认、没有堆积控制程序崩溃消息就丢了。所以它适合的场景是实时性要求高、丢消息可以接受的场景比如刷新在线人数、通知前端刷新页面。如果你需要的是可靠消息传递应该用专业消息队列或者 List BRPOP 实现的消息队列别拿 PUB/SUB 硬扛。另外一个细节要注意Subscribe 之后客户端就处于监听状态不能再执行普通命令。所以实际项目里通常是一个连接专门做订阅监听另一个连接做业务读写两者分开否则会出现“订阅了消息但其他命令发不出去”的诡异问题。4.3 EVAL 与 Lua一段脚本在 Redis 端原子执行EVAL 命令是 Redis 进阶必须会的东西。它允许你提交一段 Lua 脚本由 Redis 服务端执行整个执行过程是原子的中间不会被其他命令插队。这比 MULTI/EXEC 的“事务”靠谱得多。举一个典型场景库存扣减并记录操作日志。用普通命令你得先 DECR 库存再 LPUSH 日志两步不是原子中间崩了库存就扣了日志没记。用 Lua 脚本if redis.call(GET, KEYS[1]) 0 then redis.call(DECR, KEYS[1]) redis.call(LPUSH, KEYS[2], ARGV[1]) return 1 else return 0 end调用EVAL 脚本内容 2 stock:1001 op:log 扣减1Redis 会把这个脚本当做一个整体原子执行不会出现中间状态。这个方法在秒杀、抽奖、防超卖里都是标准解法。其实你不用死记 EVAL 的用法很多客户端库都有 eval 的封装。重点是你要知道当你发现自己需要“多条命令合起来必须是原子的”时Lua 脚本是那条正路。5. 连接、监控与排错基础命令里的救急能力一个典型的线上事故往往不是 SET/GET 本身出了错而是不知道用哪些命令去看当前 Redis 的状态。这里我把平时最常用的几个救急命令整理一遍。5.1 PING / SELECT / DBSIZE给 Redis 做“体检”PING返回 PONG 说明服务端活着。排查网络对不对、服务通不通第一件事就是 PING。SELECT切换数据库编号。默认 Redis 有 0~15 一共 16 个库但我不建议业务上用多个库。多库会让运维和排查都变复杂出了问题你都不知道在哪个库里找 key。生产环境统一用 db0 就够了。DBSIZE返回当前库的 key 数量。如果 key 数量异常增长通常是某个业务 key 没设置过期时间或者 SCAN 匹配模式的 key 在持续产生。一套比较实用的自查流程先 PING 确认服务通再 DBSIZE 看 key 总量是否异常然后用 SCAN 扫一批 key 出来看具体是哪些业务的前缀在膨胀。三步下来绝大多数“内存怎么突然涨了”的问题都能找到方向。5.2 INFO一张快照看懂 Redis 全局状态INFO是 Redis 提供的一个大而全的统计命令按 section 返回不同维度的信息。我平时最常看的是这几个INFO memory # 内存使用情况 INFO stats # 命中率、命令调用次数 INFO clients # 连接数 INFO replication # 主从复制状态 INFO keyspace # 每个库的 key 数量重要指标里used_memory_human直接看内存占用keyspace_hits和keyspace_misses看缓存命中率connected_clients看连接数是否异常。命中率如果长期偏低说明你的缓存设计有问题大量请求都打到了数据库该考虑调整过期时间或缓存策略了。需要说明的是INFO的输出在 Redis 不同版本里字段名略有差异但关键项基本不变。线上排查时别只盯着 error logINFO 里的数字能告诉你非常多东西。5.3 SLOWLOG揪出慢命令的元凶Redis 执行命令是单线程的任何一个命令耗时过长都会影响所有请求。SLOWLOG就是用来查“哪些命令跑得慢”的。SLOWLOG GET 10 # 显示最近 10 条慢命令 SLOWLOG LEN # 慢日志数量 SLOWLOG RESET # 清空慢日志慢命令的阈值由slowlog-log-slower-than配置默认 10000 微秒10 毫秒。凡是大于这个值的命令都会被记录。排查命令超时问题时第一站就该看 SLOWLOG而不是先怀疑网络。我看到过的很多“Redis command timed out”事故根因其实都是慢命令——比如上面说的 KEYS 全表扫描、HGETALL 大对象、DEL 大 key而不是客户端到服务器之间的网络问题。5.4 MONITOR威力巨大但用不好就是二次事故MONITOR会实时打印 Redis 收到所有命令可以用来观察线上实际的命令流向。我之前定位某个 key 被莫名删除就是开 MONITOR 盯了几十秒看到一条来自某个业务端的 DEL 命令瞬间定位到调用方。但请你务必记住MONITOR 在高流量环境下本身就是一把杀器它会极大地拖慢 Redis 性能因为它要把每一条命令都实时输出到客户端。我在生产环境开 MONITOR 从来不超过 5 秒确认完问题立刻CtrlC停掉。如果要长时间观测更稳妥的方案是用SLOWLOG或者客户端侧的访问日志而不是 MONITOR。5.5 CLIENT LIST / CLIENT KILL管理连到 Redis 的客户端CLIENT LIST # 列出所有连接 CLIENT KILL IP:port # 杀掉指定连接 CLIENT SETNAME my-app # 给连接设置名字非常好用一个非常实用的小习惯客户端连接时调用CLIENT SETNAME。线上排查时CLIENT LIST里能看到每个连接来自哪个应用否则一堆连接全部长一个样根本分辨不了是谁家客户端把连接池占满了。5.6 一套完整的“命令超时”排查链路热词的搜索榜里出现了 “redis command timed out; nested exception is io.lettuce.core.RedisCommandTimedOut”这其实是很多用 Spring Boot Lettuce 的团队最常见的线上问题。我经历过几次之后沉淀出一套排查链路按顺序做基本都能定位第一步确认是局部超时还是全局超时。如果所有请求都超时大概率是 Redis 本身出问题如果只有个别请求超时先看网络和客户端配置。第二步看SLOWLOG GET 20判断是否有人在执行大 key 相关命令。一次涉及几百万元素的 HGETALL 或 KEYS足以让主线程停下来几百毫秒客户端比如 Lettuce 如果超时时间设得短就会抛 timeout 异常。第三步看CLIENT LIST确认连接数是否异常暴涨。Lettuce 默认线程模型是共享连接但如果配置不当或者线程池被打满也可能出现等待超时。第四步看INFO stats里的total_commands_processed是否在增长。如果命令处理量没有明显增长但请求却在超时可能问题在网络层比如跨机房访问 Redis 的延迟突然飙升。第五步容易被忽略检查操作系统的文件句柄限制和 TCP backlog。连接数暴涨时Redis 可能因半连接队列溢出而拒绝新连接表现也是命令超时。这个链路我每次遇到 Redis 相关超时都会过一遍基本能在十分钟内锁定大头问题。你也可以截图存着下次遇到直接照着跑。6. 结合真实使用经验几条常用命令的组合实战最后这一章我挑选三个最典型的“命令组合”场景展开。它们不是单个命令的问题而是把多个命令串成一个完整的解决方案。6.1 用 SET NX EX 实现分布式锁分布式锁是 Redis 经典应用核心命令是SET lock:order:1001 token_value NX EX 30参数拆解如下NX仅当 key 不存在时设置成功。如果已经有人持有锁这条命令返回 nil说明获取锁失败。EX 30锁在 30 秒后自动过期防止持有锁的进程崩溃导致死锁。token_value锁的持有者标识可以用 UUID释放锁时用来确认“这个锁还是我的”。释放锁时要格外小心。最容易犯的错是直接 DELDEL lock:order:1001这么做有个隐患如果锁已经因为执行业务超时而自动过期而后来的请求 B 获取了同一把锁这时 A 执行完业务再 DEL就把 B 的锁误删了。正确做法是“先校验 token 再删除”但 GET 和 DEL 是两步不是原子的。所以释放锁得用 Lua 脚本if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end只有发现自己持有的 token 依然存在时才删除。这个方案不能说是完美的但在绝大多数业务里已经是很好的平衡了。可如果你做的是高可靠的金融级场景建议引入 Redlock 或者直接用专门的分布式协调组件单实例 Redis 锁在这类场景里还是不够稳。6.2 缓存治理三个命令组合扛住热点流量从热词里我注意到有不少人在搜 “redis 缓存治理”。这里的核心问题通常是缓存穿透、缓存击穿、缓存雪崩。对应到命令层面我推荐一套在实践中验证过多次的组合先说缓存击穿某个热点 key 过期瞬间大量请求直接打到数据库。应对思路是“互斥重建”即同一时刻只允许一个线程去数据库查数据并写回缓存其他线程先等。这条逻辑放在命令层面就是先用SET cache:hot:1001 empty NX EX 10抢一个“正在重建”的标记抢到的线程才去查库并回填缓存。这个方案在 Redis 分布式锁的基础上加一层非常实用。再说缓存雪崩大量 key 同时过期导致数据库压力瞬间翻倍。命令层面能做的就是我们第 3 章说的设置过期时间时加上随机偏移。最后说缓存穿透查询一个不存在的 key缓存和数据库都没有请求每次都打到数据库。命令层面的应对是查库结果为空时也把这个空结果写入缓存并用较短过期时间比如 60 秒这样同样一个不存在 key 的查询会被缓存挡住。写空缓存的命令就是常见的SETEX cache:empty:xxx 60。这三个问题每一个都有对应的 Redis 命令组合不需要引入额外的组件就能显著改善。真正到高并发场景可能还需要布隆过滤器之类的方案但基础缓存治理先用这几个命令组合基本能解决 80% 的问题。6.3 批量删除 key 的正确姿势运营或者开发经常会提一个需求“把所有user:temp:*的 key 清理掉。”新手可能会写KEYS user:temp:* | xargs redis-cli DEL这个写法有两个问题KEYS 会阻塞管道方式又慢又不安全。我的标准做法是写一个小脚本用 SCAN 游标遍历 UNLINK 异步删除redis-cli --scan --pattern user:temp:* | xargs -L 100 redis-cli UNLINK这里的-L 100意思是每 100 个 key 执行一次 UNLINK既能批量又不会因为一次传太多参数导致命令行过长。整个过程对线上 Redis 的影响被控制在很小范围我在千万级 key 的实例上执行过没有任何阻塞告警。如果你用的是 Python 等语言用客户端的 scan 接口配合 pipeline 做同样的事逻辑也是完全一样的。核心心法就一条能游标扫就别全量查能异步删就别同步删。7. 我最后想说的话基础命令没那么难难的是知道“什么时候不该用”如果让我把 Redis 基础命令的整体感受浓缩成本人最真实的经验那就是开头说的那句话命令真的不多也不难记。你真正需要建立的是对每种数据类型的“形状感”——这个 key 后面挂的是一个字符串、一张表、一条链表、一个集合还是一棵有序跳表以及一种“安全直觉”——任何全量操作、任何同步删除、任何阻塞命令在高流量生产环境里都可能变成事故源。我在实际使用中最受益的一个小习惯是给 redis-cli 做一个默认配置别名把默认超时时间和认证写进环境变量这样每次进命令行时不会因为少传参数而暴露密码也不会因为默认超时太短误判网络问题export REDISCLI_AUTHyour_password alias redis-cliredis-cli -t 3000另外强烈建议你平时练习的时候用redis-cli --raw避免二进制数据乱码生产环境排查时配合INFO、SLOWLOG、CLIENT LIST一起看而不是单看一条命令的输出。Redis 命令学到最后拼的不是你记得多少条命令而是你能不能在用错之前先意识到“这个场景根本不该用这条命令”。