
很多人接触 Redis都是从 set/get 开始的也止步于 set/get。等到系统压测一上来缓存穿透、热点 key、主从切换、大 key 扫库这些问题冒出来才发现自己除了这几个基础命令对 Redis 的命令体系几乎没什么概念。Redis 官方文档里有 200 多条命令真正决定系统上限的恰恰是那些平时不起眼、关键时刻能救命的命令。这篇文章不是把命令手册抄一遍而是按我这些年实际用 Redis 的经验把最值得掌握的 redis 命令按场景拆开讲从基础数据类型到主从集群、从分布式锁到线上排查顺便聊聊面试里常考的那些点。适合刚入门的同学也适合用了很久 Redis 但一直只会在客户端里点点点的同事。1. 命令的底层协议与通用约定先把“玩法”搞清楚1.1 redis-cli 与 RESP命令为什么长这样Redis 的命令通过网络发送给服务器底层协议叫 RESPREdis Serialization Protocol。很多人不需要关心这一层但当你用代码里的客户端或者 redis-cli 敲命令的时候本质上都是在构造一串字符发给 RedisRedis 再按格式把结果返回。RESP 协议其实很简单命令和参数之间用 \r\n 分隔第一个是命令名后面是参数。举个例子你在 redis-cli 里敲127.0.0.1:6379 SET name zhangsan OK实际发到服务器的是*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$8\r\nzhangsan\r\n*3表示后面有 3 个字符串$3表示接下来字符串长度是 3。这个设计非常轻量对一个内存数据库来讲解析成本可以忽略不计。这也是为什么在命令行里看到的命令总是“动词 key 参数”的结构比如 SET、GET、DEL、TYPE。明白了协议你在排查问题时会更有底气。比如客户端连不上、命令超时你可以直接用 nc 或者 telnet 这种方式向 6379 端口发送一组 RESP 数据看服务器到底有没有正常工作。这在个别极端环境下比装一个 redis-cli 更管用但属于偏门技巧日常调试直接用 redis-cli 就够了。1.2 返回值决定客户端行为nil、整数、数组Redis 命令的返回值大致有几种简单字符串如 OK、错误消息、整数、批量字符串、数组、nil。这个细节在写代码时特别重要。举一个最常见的例子GET 一个不存在的 key返回的是 nil而不是空字符串。如果你的业务代码这么写String v redis.get(key); if (v null) { // 缓存不存在 }Redis 返回 nil 会让 Java 客户端的 get 方法返回 null。如果你没搞清楚这一点不小心把空字符串写进缓存再读出来就不是 null判断逻辑直接翻车。再看 INCR 命令它返回的是自增后的整数。如果你要看自增前的值只能先 GET 再 INCR但这两个操作不是原子的。实际场景里都是用 INCRBY 做增减不要纠结“自增前的结果”命令返回值设计本身就告诉你它给的是当下最新状态。还有一类常见命令返回数组比如 LRANGE、SMEMBERS、HGETALL它们把多个结果打包返回。客户端在解析时是阻塞等待完整结果的如果一个 key 的列表有几百万个元素这个命令会直接在内存里组装完所有结果再返回。线上出现卡顿很多原因就在这。1.3 命令扩展参数EX、NX、XX、KEEPTTL 这些尾巴怎么用SET 命令在最简单用法之外有一堆扩展参数SET key value [EX seconds | PX milliseconds] [NX | XX] [KEEPTTL]EX seconds设置过期时间单位秒。PX milliseconds过期时间单位毫秒。NX只在 key 不存在时设置相当于老命令 SETNX。XX只在 key 已存在时设置类似 SETEX 但保留原值判断。KEEPTTL设置新值时保留原来的过期时间。这些参数最大的意义是省去“先判断再设置”的多命令组合。比如实现一个互斥锁redis-cli SET lock_key request_id NX EX 10这一个命令就把“不存在才设置”和“10 秒自动过期”两个条件原子地完成。如果先 SETNX 再 EXPIRE中间一旦进程崩溃锁就永远不会过期线上事故就是这么来的。Redis 6.0 之后SET 的 NX/XX 和 EX 已经能替代老旧的 SETNX、SETEX老命令保留只是兼容。新代码我建议直接用 SET 的扩展参数代码更统一。还有 GETDEL获取值并删除这种命令能省一次网络往返也是值得留意的小技巧。2. 五大数据类型命令业务选型时的“对应表”这一章是重头戏。我不打算把命令表全抄过来而是按业务场景讲每一类命令的实战位置。2.1 字符串计数器、位图、简单缓存字符串是 Redis 最基础的类型底层是动态字符串 SDS最大 512MB。最常用的命令除了 SET/GET还有INCR/DECR/INCRBY/DECRBY自增自减原子操作。INCRBYFLOAT浮点数自增。APPEND追加内容。STRLEN查看字符串长度。GETDEL取完值直接删。SETEX/PSETEX设置值并指定过期时间。实际场景里INCR 经常用来做计数器、限流、发号器。比如接口限流可以这么写127.0.0.1:6379 INCR api:limit:user_1001 (integer) 1第一次访问返回 1如果超过 100后面再配合 EXPIRE 设置窗口时间就能实现固定窗口限流。虽然是简易方案但很多内部系统这么用完全够用。字符串里还有一个经典技巧是位图操作。用 SETBIT / GETBIT / BITCOUNT / BITOP 可以在一个字符串上用 bit 做状态位判断。比如统计一个月的签到情况每个用户一个 key第几天签到就把对应 bit 位设为 1最后用 BITCOUNT 直接统计本月签到天数。这种做法的优点非常明显一个用户一年只需要 365 bit不到 50 字节。但前提是你对位运算有基础否则后期排查数据很容易看花眼。2.2 哈希对象缓存时的字段级操作哈希对应的是业务里的对象key 是对象 IDfield 是对象的属性。命令主要有HSET key field value/HMSET批量设置字段。HGET key field/HMGET批量获取字段。HGETALL获取所有字段和值。HINCRBY对字段做自增适合点赞数、库存数。HDEL删除字段。HLEN字段数量。HEXISTS判断字段是否存在。用哈希做对象缓存比直接把整个对象序列化成 JSON 字符串好处多你可以只更新某个字段不用把整个对象读出来改完再写回。比如用户积分变动HSET user:1001 score 250这一个命令就完成了避免并发下的读改写覆盖。不过这里有个隐蔽的坑HGETALL 会把所有字段一次性返回当 field 特别多、数据量特别大时这条命令产生的响应体可能把网卡打满。取个别字段用 HMGET遍历使用 HSCAN别图省事直接 HGETALL。这是我排查线上故障时看过很多次的问题。2.3 列表消息队列与时间线列表底层是双向链表支持左进右出命令也很有规律LPUSH/RPUSH从左侧或右侧压入。LPOP/RPOP从左侧或右侧弹出。LRANGE key start stop获取区间元素。LINDEX/LSET/LTRIM按索引操作。LLEN获取长度。BLPOP/BRPOP阻塞弹出超时时间内如果列表没有数据就一直等。列表最经典的用途是消息队列的雏形。生产者 LPUSH 消息消费者 BRPOP 阻塞取消息天然实现 FIFO 排队。BRPOP 的好处是当队列为空时客户端不会白白空转而是阻塞等待有数据到达立即被唤醒对资源非常友好。但注意LPUSH BRPOP 搭建的队列没有 ACK 机制。消费者取走消息后如果处理失败消息就丢了。需要可靠投递的话要么自己实现“取走 处理 删除”的两阶段模式要么直接用专门的消息中间件。把 Redis 列表当消息队列用适合内部、允许少量丢失的场景不适合核心交易链路。2.4 集合标签、交集、随机抽奖集合是无序、去重的字符串集合。命令主要有SADD/SREM/SPOP/SMOVE。SCARD数量。SISMEMBER判断是否存在。SMEMBERS获取所有成员。SINTER/SUNION/SDIFF交集、并集、差集。SRANDMEMBER随机返回成员。集合最大的价值是集合运算。比如社交系统里你关注的用户存在set:user:1001:following粉丝存在set:user:1001:fans“互相关注”就可以用 SINTER 一行命令算出来。标签系统同理给文章打标签SADD article:123 tags python redis想找出同时含 python 和 redis 标签的文章用多个集合做交集。抽奖场景里SRANDMEMBER 适合“抽了不删除”SPOP 适合“抽完从池子移除”这个区别要分清楚。另外 SMEMBERS 同样有大数据量问题一次性返回几百万成员会让阻塞和网络开销爆发只适用于小集合大集合遍历用 SSCAN。2.5 有序集合排行榜与延迟队列有序集合是 Redis 里功能最丰富的数据类型成员附带分数按分数排序。命令ZADD key score member。ZRANGE/ZREVRANGE按排名取成员。ZRANGEBYSCORE/ZREVRANGEBYSCORE按分数区间取成员。ZSCORE查分数、ZINCRBY对分数做自增。ZREM删除成员、ZCARD数量。ZRANK/ZREVRANK查排名。最常见的用途是排行榜ZADD ranking 100 user_1用户分数变化后ZINCRBY ranking 10 user_1查询 TOP10 就ZREVRANGE ranking 0 9 WITHSCORES一步到位。底层是跳表性能非常稳定。有序集合还有一个玩法——延迟队列。score 存事件要执行的时间戳轮询时用ZRANGEBYSCORE key -inf 当前时间戳取出所有到期事件再用 ZREM 删除。相比专业延迟队列少了后台调度和重试机制但在轻量级自研系统里非常够用而且能精确到毫秒。3. 过期、持久化与内存命令把数据生命周期管起来3.1 EXPIRE 与 TTL从过期删除策略到缓存雪崩缓存数据一定要设置过期时间。给 key 设置过期时间用EXPIRE key seconds或PEXPIRE key milliseconds查看剩余时间用TTL key或PTTL key。TTL 返回 -1 表示 key 永不过期返回 -2 表示 key 已经不存在这两个返回值很容易混淆写判断时要注意。Redis 的过期删除是“惰性删除 定期删除”的组合。惰性删除是指访问 key 时才检查是否过期并删除定期删除是后台每 100ms 随机抽样一批过期的 key 删除。这套机制的好处是避免为删除过期 key 专门扫描全库但代价是大量过期 key 在没有被访问也没有被定期抽中的情况下会一直占用内存。理解了这套机制你就知道为什么会有缓存雪崩。如果一大批 key 设置了相同过期时间比如凌晨 0 点统一过期0 点之后的第一个请求可能全部打到数据库上瞬间打爆。常用的命令级解法是给过期时间加随机偏移redis-cli SET cache_data value EX 3600业务层生成过期时间时再叠加一个 0 到 300 秒的随机数。这样同一