Redis命令体系实战详解:从set/get到分布式锁与线上排查
2026/9/10 7:44:50 网站建设 项目流程

很多人接触 Redis,都是从 set/get 开始的,也止步于 set/get。等到系统压测一上来,缓存穿透、热点 key、主从切换、大 key 扫库这些问题冒出来,才发现自己除了这几个基础命令,对 Redis 的命令体系几乎没什么概念。Redis 官方文档里有 200 多条命令,真正决定系统上限的,恰恰是那些平时不起眼、关键时刻能救命的命令。这篇文章不是把命令手册抄一遍,而是按我这些年实际用 Redis 的经验,把最值得掌握的 redis 命令按场景拆开讲,从基础数据类型到主从集群、从分布式锁到线上排查,顺便聊聊面试里常考的那些点。适合刚入门的同学,也适合用了很久 Redis 但一直只会在客户端里点点点的同事。

1. 命令的底层协议与通用约定:先把“玩法”搞清楚

1.1 redis-cli 与 RESP:命令为什么长这样

Redis 的命令通过网络发送给服务器,底层协议叫 RESP(REdis Serialization Protocol)。很多人不需要关心这一层,但当你用代码里的客户端或者 redis-cli 敲命令的时候,本质上都是在构造一串字符发给 Redis,Redis 再按格式把结果返回。

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 是对象 ID,field 是对象的属性。命令主要有:

  • 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 secondsPEXPIRE key milliseconds,查看剩余时间用TTL keyPTTL 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 秒的随机数。这样同一

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询