Redis 支持五种基本数据类型,每种数据类型都有其特定的应用场景和操作命令。理解这些数据类型的特点和使用方法是掌握Redis的关键。
==字符串== (String)
字符串的特点和应用
字符串是Redis 最基本的数据类型,也是最常用的类型。在 Redis 中,字符串是二进制安全的,可以存储任何数据,包括数字、文本、序列化对象、甚至是图片。
主要特点:
- 二进制安全:可以存储任何二进制数据
- 最大长度:单个字符串最大
512MBR - 编码优化:
Redis会根据内容自动选择最优编码 - 原子操作:所有字符串操作都是原子性的
常见应用场景:
- 缓存数据(
JSON、XML、HTML 等) - 计数器(访问量、点赞数等)
- 分布式锁
- 会话存储
- 配置信息存储
基本操作命令
设置和获取
# 设置键值
# 用法:SET key value
SET name "张三"
SET age 25
# 获取值
# 用法:GET key
GET name # 返回 "张三"
GET age # 返回 "25"
GET nonexistent # 返回 (nil)
# 批量设置
# 用法:MSET key1 value1 key2 value2 key3 value3
MSET user:1:name "张三" user:1:age 25 user:1:city "北京"
# 批量获取
# 用法:MGET key1 key2 key3
MGET key1 key2 key3
MGET user:1:name user:1:age user:1:city
# 设置并返回旧值
# 用法:GETSET key newvalue
GETSET key newvalue
GETSET counter 100 # 返回旧值,设置新值为100
设置和获取 (SET / GET)
这是 Redis 最基础的读写操作。
SET key value- 作用:设置一个键(key)的值(value)。如果 key 已存在,会覆盖旧值。
- 图片示例:
SET name "张三":存入一个键name,值为字符串"张三"。SET age 25:存入一个键age,值为25。- 💡 注意:在
Redis中,数字也是以字符串形式存储的。
GET key- 作用:获取指定键的值。如果键不存在,返回
nil(空)。 - 图片示例:
GET name:返回"张三"。GET age:返回"25"。GET nonexistent:因为不存在这个键,所以返回(nil)。
- 作用:获取指定键的值。如果键不存在,返回
批量设置 (MSET – Multi SET)
当你需要同时设置多个键值对时,用这个命令可以减少网络请求次数,提高效率。
MSET key1 value1 key2 value2 ...- 作用:同时设置一个或多个 key-value 对。
- 图片示例:
MSET user:1:name "张三" user:1:age 25 user:1:city "北京"- 这里使用了冒号
:作为命名空间分隔符。这是一种很好的编程习惯,它实际上存了三个键:user:1:name、user:1:age、user:1:city,看起来就像是一个用户的属性集合。
批量获取 (MGET – Multi GET)
与 MSET 对应,用于一次性获取多个键的值。
MGET key1 key2 key3 ...- 作用:同时获取一个或多个 key 的值。返回一个列表,顺序与请求的 key 顺序一致。如果某个 key 不存在,对应位置返回
nil。 - 图片示例:
MGET user:1:name user:1:age user:1:city- 这会一次性把刚才存进去的三个值全部取出来,返回结果类似:
1) "张三" 2) "25" 3) "北京"。
- 作用:同时获取一个或多个 key 的值。返回一个列表,顺序与请求的 key 顺序一致。如果某个 key 不存在,对应位置返回
设置并返回旧值 (GETSET)
这是一个结合了“修改”和“查询”的原子操作。
GETSET key newvalue- 作用:将给定 key 的值设为
newvalue,并返回 key 的旧值。 - 图片示例:
GETSET counter 100:把counter的值设为100,同时返回它之前的值(如果有的话,比如返回"50")。- 💡 这个命令在需要重置计数器(比如每天清零)并获取昨日统计数据时非常有用。
- ⚠️ 注意:在较新的
Redis版本中,官方更推荐使用SET key value GET来代替GETSET,因为功能更强大且更通用。
- 作用:将给定 key 的值设为


条件设置
# 仅当键不存在时设置
# 用法:SETNX key value
SETNX lock:resource "server1" # 分布式锁应用
# 设置键值和过期时间
# 用法:SETEX key seconds value
SETEX session:abc123 3600 "user_data" # 3600秒后过期
# 仅当键不存在时设置,并指定过期时间
# 用法:SET key value EX seconds NX
SET lock:order:1001 "processing" EX 30 NX
# 仅当键存在时设置
# 用法:SET key value XX
SET existing_key "new_value" XX

RedisString 类型高级应用:锁、会话与条件更新
1. 核心命令演进
| 命令语法 | 核心作用 | 原子性 | 缺陷/适用场景 | 推荐指数 |
|---|---|---|---|---|
SETNX key value |
仅当 key 不存在时设置(互斥锁雏形) | 是 | 极易死锁。若客户端宕机未释放,锁永远存在。 | ⭐ (已淘汰,仅供了解) |
SETEX key sec value |
设置值并指定过期时间(会话管理) | 是 | 无法做到“不存在才设置”。适用于覆盖写入、会话存储。 | ⭐⭐⭐ |
SET key val EX sec NX |
不存在才设置,且带过期时间 | 是 | 现代分布式锁的终极标准方案,完美解决死锁。 | ⭐⭐⭐⭐⭐ |
SET key val XX |
仅当 key 存在时更新(条件更新) | 是 | 防止无中生有,常用于更新已存在的键,防止脏数据。 | ⭐⭐⭐⭐ |
2. 核心命令深度剖析
1. 分布式锁的终极方案:SET ... EX ... NX
- 代码示例:
SET lock:order:1001 "server_1" EX 30 NX - 专业解析:
NX(NoteXists):保证互斥性,只有一个客户端能抢到锁。EX 30:保证防死锁。即使客户端 A 抢到锁后突然崩溃,30秒后锁自动释放,不会阻塞系统。- 原子性:早期使用
SETNX+EXPIRE是两步操作,如果在这两步之间发生宕机,依然会死锁。而SET配合EX和NX参数,是Redis底层的一条单线程原子命令,绝对安全。
2. 会话管理的神器:SETEX
- 代码示例:
SETEX session:abc123 3600 "user_data" - 专业解析:
Redis常被用作分布式 Session 缓存。SETEX强制要求设置过期时间,避免了因忘记设置过期时间导致Redis内存被无用的 Session 撑爆。
3. 条件更新的守卫:SET ... XX
- 代码示例:
SET existing_key "new_value" XX - 专业解析:
XX(eXists) 保证了只有键存在时才更新。在并发场景下,如果多个线程试图修改同一数据,XX可以防止“预期修改现有数据,结果却创建了新键”的意外情况。
==3==.Redis分布式锁(面试题)
虽然 SET key value EX 30 NX 解决了死锁和互斥问题,但在极端的高并发生产环境下,依然存在隐患,这也是大厂面试最爱问的:
⚠️ 风险:误删别人的锁
- 场景:客户端 A 抢到锁并设置 30 秒过期。但 A 的业务逻辑执行了 35 秒。第 31 秒时,锁自动过期,客户端 B 抢到了锁。第 35 秒,A 执行完毕,直接执行了
DEL lock,结果把 B 的锁给删了。 - 解决方案(
Lua脚本):解锁时,必须先GET判断当前锁的 value 是不是自己的(比如判断是不是 “server_1″),如果是,再DEL。必须用Lua脚本执行,以保证判断和删除的原子性。
lua
-- 安全的解锁 Lua 脚本逻辑
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
- (注:如果要做到绝对完美,还需要解决“锁过期了但业务没执行完”的问题,即引入
Redisson框架的看门狗(Watchdog)机制,实现自动续期。)
1. 核心概念与基石
- 为什么需要? 单机环境用
synchronized即可。但在分布式集群/微服务架构下,多进程需访问同一共享资源(如扣减库存),本地锁失效,必须引入跨进程的全局互斥锁。 - 为什么选
Redis? 基于内存、性能极高;单线程模型天然具备操作的原子性。
2、 流程与四大痛点(面试重点)
1. 阶段一:基础互斥(解决并发问题)
- 命令:
SETNX lock_key value(Set if NoteXists) - 逻辑:返回
1代表抢锁成功,0代表失败;业务结束用DEL释放。 - 致命缺陷:死锁。若客户端抢锁后崩溃,未执行
DEL,锁永远存在。
2. 阶段二:防死锁(解决过期问题)
- 错误做法:
SETNX成功后,再单独执行EXPIRE设置过期时间。这俩不是原子操作,中间宕机依然死锁。 - 正确做法:使用一条原子命令
SET lock_key value EX 30 NX。一步到位实现“不存在才设置”且“自动过期”,彻底解决死锁。
3. 阶段三:防误删(解决原子性与越界问题)
- 新隐患:业务执行时间 > 锁过期时间。客户端 A 没执行完锁就过期,B 抢到了锁;A 执行完后顺手
DEL,把 B 的锁删了。 - 解决方案:
- 唯一标识:
value存客户端唯一 ID(如UUID)。 Lua脚本:释放锁时,用Lua脚本(保证原子性)判断:若value等于自己的 ID,才执行DEL。
- 唯一标识:
4. 阶段四:自动续期(终极救星:Redisson 看门狗)
- 新隐患:如果预估不好过期时间,设短了业务没做完锁就没了;设长了客户端宕机则长时间死锁。
- 终极方案:引入
Redisson框架的 Watchdog(看门狗)机制。- 机制:不指定过期时间时(默认 30s),后台起定时任务,每 10s 检查当前线程是否存活。若存活,自动将锁重置为 30s(自动续期);若宕机,停止续期,30s 后释放。
==🐶==终极解法:Redisson 的看门狗机制 (Watchdog)
为了解决“过期时间设长了容易死锁,设短了业务没执行完”这个矛盾,Java 生态中最强大的Redis客户端——Redisson,引入了看门狗机制。
1. 它是怎么工作的?
- 不写死过期时间:当你使用
Redisson加锁时(例如lock.lock()),如果不手动指定时间,它默认的过期时间是 30 秒。 - 后台定时任务(看门狗):一旦加锁成功,
Redisson会在后台启动一个定时任务。 - 自动续期:这个“狗”每隔 10 秒(也就是过期时间的 1/3)就会去检查一下:客户端 A 还在执行吗?如果是,它就会给锁重新设置为 30 秒。相当于无限续杯。
- 停止续期:一旦客户端 A 的业务执行完毕,主动调用
unlock(),或者客户端 A 真的直接宕机了(那么“狗”也跟着死了,无法续期),锁就会在 30 秒后真正过期释放。
2. 核心逻辑图解
text
客户端 A 抢到锁 (过期时间 30s)
|
+---> 启动看门狗
|
+---> 执行业务 (耗时 40s)
|
+--- 第 10s:看门狗检查 -> A 还在运行 -> 续期到 30s
+--- 第 20s:看门狗检查 -> A 还在运行 -> 续期到 30s
+--- 第 30s:看门狗检查 -> A 还在运行 -> 续期到 30s
+--- 第 40s:业务执行完毕,主动解锁。
|
+---> 看门狗停止。
💡 面试总结(必背)
“
Redis分布式锁的演进路径是:SETNX实现基础互斥,SET NX EX解决死锁,Lua 脚本 + 唯一 ID解决误删,Redisson 看门狗解决业务超时自动续期。 在工程落地中,Java 体系首选引入Redisson框架。”
字符串操作
# 追加字符串
# 用法:APPEND key value
SET message "Hello"
APPEND message " World" # message 现在是 "Hello World"
# 获取字符串长度
# 用法:STRLEN key
STRLEN message # 返回 11
# 获取子字符串
# 用法:GETRANGE key start end
GETRANGE message 0 4 # 返回 "Hello"
GETRANGE message -5 -1 # 返回 "World"
# 设置子字符串
# 用法:SETRANGE key offset value
SETRANGE message 6 "Redis" # message 现在是 "Hello Redis"
追加字符串 (append)
SET message "Hello"
APPEND message " World
SET message "Hello":将键message的值设置为字符串"Hello"。APPEND message " World":将值" World"追加到键message现有值的末尾。- 执行结果:
message的值变为"Hello World"。该命令返回追加后字符串的总长度(即 11)。
获取字符串长度 (strlen)
STRLEN message
STRLEN message:获取键message所对应字符串的字节长度。- 执行结果:返回
11(对应 “Hello World” 11个英文字符)。 - 注:由于
Redis采用UTF-8编码,如果值是中文(如“张三”),返回的长度将是 6(每个中文字符占 3 个字节)。
获取子字符串 (getrange)
GETRANGE message 0 4
GETRANGE message -5 -1
GETRANGE message 0 4:截取键message的值,从索引0开始,到索引4结束(包含索引 4)。- 执行结果:返回
"Hello"。 GETRANGE message -5 -1:使用负数索引截取。-1代表最后一个字符,-5代表倒数第 5 个字符。- 执行结果:返回
"World"。
设置子字符串 (setrange)
SETRANGE message 6 "Redis"
SETRANGE message 6 "Redis":从索引6(即第 7 个字符)开始,用字符串"Redis"覆盖键message原有的字符。- 执行结果:原值
"Hello World"的索引 6 到 10 的"World"被替换,message的值变为"Hello Redis"。该命令返回修改后字符串的总长度。

数值操作
Redis 可以将字符串当作数字进行操作,支持整数和浮点数。
# 整数操作
SET counter 10
# 增加指定值
INCR counter # counter = 11
INCRBY counter 5 # counter = 16
DECR counter # counter = 15
DECRBY counter 3 # counter = 12
# 浮点数操作
SET price 19.99
INCRBYFLOAT price 5.01 # price = 25.00
INCRBYFLOAT price -2.50 # price = 22.50
# 应用示例:网站访问计数
INCR page:home:views # 通常用于统计首页的访问量(PV)
INCR user:1001:login_count #
INCRBY article:123:likes 1

==列表== (List)
列表的特点和应用
Redis列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表的头部或尾部。
主要特点:
- 有序性:元素按插入顺序排列
- 可重复:允许重复元素
- 双端操作:支持头部和尾部操作
- 最大长度:理论上可包含 2^32 – 1 个元素
常见应用场景:
- 消息队列
- 最新消息列表
- 用户操作历史
- 任务队列
- 时间线功能
基本操作命令
Redis四大核心字母缩写
1. NX = Not eXists(不存在)
- 中文含义:仅当键不存在时才执行操作(无中生有)。
- 常见命令:
SETNX、SET key value NX - 核心场景:抢锁、防覆盖。只有当前没人占用这个坑(键不存在),我才能把名字写进去。
- 反向兄弟:
XX(eXists),仅当键存在时才执行(锦上添花,常用于更新数据防止脏插)。
2. EX = EXpire/ EXpires(过期)
- 中文含义:设置键的过期时间(单位:秒)。
- 常见命令:
SETEX、SET key value EX 30 - 核心场景:防死锁、存会话。给数据加一个“定时炸弹”,时间一到,
Redis自动把数据删掉。
3. L / R = Left / Right (左 / 右)
- 中文含义:代表操作的方向(L 代表从左边/头部,R 代表从右边/尾部)。
- 常见命令:
LPUSH/RPUSH(从左/右塞入)LPOP/RPOP(从左/右弹出)
- 核心场景:队列/栈操作。
- 用
LPUSH进,RPOP出,就是先进先出(FIFO,消息队列)。 - 用
LPUSH进,LPOP出,就是后进先出(LIFO,栈)。
- 用
- (注:如果命令里没有 R,只有单独的 L,比如
LRANGE,这里的 L 就是纯指 List 数据类型)。
4. B = Block(阻塞)
- 中文含义:==阻塞等待。命令加上 B,就意味着如果拿不到数据,程序不会立刻返回空,而是原地“死等”,直到有数据来或超时==。
- 常见命令:
BLPOP、BRPOP、BRPOPLPUSH - 核心场景:==高可用消息队列。生产者没发消息时,消费者就挂起(不占用 CPU 疯狂死循环),一旦有消息,瞬间唤醒并拿走==。
添加元素
# 从左侧(头部)添加
# 用法:LPUSH key element [element ...]
LPUSH messages "消息3" "消息2" "消息1"
# 结果:["消息1", "消息2", "消息3"]
# 从右侧(尾部)添加
# 用法:RPUSH key element [element ...]
RPUSH messages "消息4" "消息5"
# 结果:["消息1", "消息2", "消息3", "消息4", "消息5"]
# 仅当列表存在时添加
# 用法:LPUSHX key element
# 用法:RPUSHX key element
LPUSHX messages "消息6"
RPUSHX messages "消息7"
从左侧(头部)添加:LPUSH
# 用法: LPUSH key element [element ...]
LPUSH messages "消息3" "消息2" "消息1"
- 原理:
L代表 Left。Redis会按你书写的顺序,将元素依次压入列表的最左侧。 - 执行过程:先压入“消息3”,再把“消息2”压到最前面,最后把“消息1”压到最前面。
- 返回结果:
["消息1", "消息2", "消息3"](注意顺序是反过来的)。
从右侧(尾部)添加:RPUSH
# 用法: RPUSH key element [element ...]
RPUSH messages "消息4" "消息5"
- 原理:
R代表 Right。将元素依次追加到列表的最右侧(尾部)。 - 执行结果:在原有列表基础上追加,最终变为
["消息1", "消息2", "消息3", "消息4", "消息5"]。
仅当列表存在时添加:LPUSHX / RPUSHX
# 用法: LPUSHX key element
# 用法: RPUSHX key element
LPUSHX messages "消息6"
RPUSHX messages "消息7"
- 原理:带有
X(eXists)后缀。只有当messages这个键已经存在时,才会执行添加。 - 特性:如果前面没有创建
messages列表,这两条命令直接返回0且什么都不做。防止因为拼写错误生成无用的空列表。
获取元素
# 获取指定范围的元素
# 用法:LRANGE key start stop
LRANGE messages 0 -1 # 获取所有元素
LRANGE messages 0 2 # 获取前3个元素
LRANGE messages -3 -1 # 获取最后3个元素

删除元素
# 从左侧(头部)删除
# 用法:LPOP key
LPOP messages # 弹出并返回第一个元素
# 从右侧(尾部)删除
# 用法:RPOP key
RPOP messages # 弹出并返回最后一个元素
# 在指定元素前/后插入
# 用法:LINSERT key BEFORE|AFTER pivot element
LINSERT messages BEFORE "消息3" "消息2.5"
从左侧(头部)删除:LPOP
# 用法: LPOP key
LPOP messages
- 详细解释:
L代表 Left。这个命令会将列表最左边(头部)的第一个元素移除,并将这个被移除的元素返回给你。
从右侧(尾部)删除:RPOP
# 用法: RPOP key
RPOP messages
- 详细解释:
R代表 Right。这个命令会将列表最右边(尾部)的最后一个元素移除,并返回给你。
在指定元素前/后插入
# 在指定元素前/后插入
# 用法: LINSERT key BEFORE|AFTER pivot element
LINSERT messages BEFORE "消息3" "消息2.5"
- 注意:
LINSERT其实是插入命令 - 详细解释:它的作用是在列表中找到指定的基准元素(pivot),然后在它的前面(BEFORE)或后面(AFTER)插入新元素。
- 举例:假设列表现在是
["A", "B", "C"]。- 执行
LINSERT messages BEFORE "B" "X"。 - 结果会变成
["A", "X", "B", "C"]。
- 执行
- 特别注意:如果基准元素(比如图里的 “消息3″)在列表里找不到,这个命令会返回
-1,代表插入失败。

获取元素
# 获取指定范围的元素
# 用法:LRANGE key start stop
LRANGE messages 0 -1 # 获取所有元素
LRANGE messages 0 2 # 获取前3个元素
LRANGE messages -3 -1 # 获取最后3个元素
# 获取指定索引的元素
# 用法:LINDEX key index
LINDEX messages 0 # 获取第一个元素
LINDEX messages -1 # 获取最后一个元素
# 获取列表长度
# 用法:LLEN key
LLEN messages # 返回列表长度
获取指定索引的元素:LINDEX
如果你只需要列表里的某一个特定位置的元素,用这个命令性能更高。
LINDEX messages 0- 解释:获取列表的第一个元素(最左边)。
LINDEX messages -1- 解释:获取列表的最后一个元素(最右边)。
- ⚠️ 避坑提示:如果索引超出了列表长度(比如列表只有 3 个元素,你查索引
5),它不会报错,而是返回(nil)(空)。
获取列表长度:LLEN
LLEN messages- 解释:返回当前列表中包含的元素总个数(Length)。如果键不存在,返回
0。
- 解释:返回当前列表中包含的元素总个数(Length)。如果键不存在,返回

删除元素
# 从左侧(头部)弹出
# 用法:LPOP key
LPOP messages # 弹出并返回第一个元素
# 从右侧(尾部)弹出
# 用法:RPOP key
RPOP messages # 弹出并返回最后一个元素
# 删除指定值的元素
# 用法:LREM key count element
LREM messages 1 "消息2" # 从头开始删除1个"消息2"
LREM messages -1 "消息2" # 从尾开始删除1个"消息2"
LREM messages 0 "消息2" # 删除所有"消息2"
# 保留指定范围的元素
# 用法:LTRIM key start stop
LTRIM messages 0 99 # 只保留前100个元素
精准删除指定值(lrem 重点!)
LREM` 是 List 中最灵活的删除命令,它的核心在于中间的 `count` 参数:
**语法**:`LREM key count element
LREM messages 1 "消息2"- 解释:
count为正数1。表示从左往右(从头开始)找,删除1个值为"消息2"的元素。
- 解释:
LREM messages -1 "消息2"- 解释:
count为负数-1。表示从右往左(从尾开始)找,删除1个值为"消息2"的元素。
- 解释:
LREM messages 0 "消息2"- 解释:
count为0。表示无视方向,删除列表中所有值为"消息2"的元素。 - 💡 重点考点:
LREM的返回值是实际被删除的元素个数。
- 解释:
# 1. 先创建一个包含多个 "A" 的列表
RPUSH test "A" "B" "A" "C" "A"
# 此时列表结构为: ["A", "B", "A", "C", "A"]
# 2. 使用 count = 2 删除
LREM test 2 "A"
- 原理:
Redis从左边开始找,遇到了第 1 个 “A”(删掉),继续找,遇到了第 2 个 “A”(删掉),此时已经达到了count = 2的限制,命令立刻停止。 LREM key -2 "元素"的核心意思就是:从右边(尾部)开始往左边找,删掉最近遇到的 2 个指定元素。
截断列表(LTRIM )
这个命令不会按“值”删,而是按“范围”删,只保留指定区间内的元素,区间外的全删掉。
LTRIM messages 0 99- 解释:只保留索引
0到99的元素(即前 100 个元素),第 101 个及以后的元素全部被丢弃。 - 💡 场景极其常见:用于实现“最新 N 条记录”。比如你有一个保存用户操作日志的列表,为了防内存溢出,每次
LPUSH插入新日志后,顺便执行一下LTRIM key 0 99,这样就永远只保留最新的 100 条日志。
- 解释:只保留索引

修改元素
# 设置指定索引的元素值
# 用法:LSET key index element
LSET messages 0 "新消息1"
重新设置索引值:LSET
- 语法:
LSET key index element - 代码示例:
LSET messages 0 "新消息1" - 详细解释:
- 这个命令的作用是:将列表
messages中,索引为0(即第一个元素)的值,强制替换为"新消息1"。 - 它不会改变列表的长度,==只是把那个位置上的旧值给覆盖掉了==(类似于数组的赋值操作)。
- 如果修改成功,Redis 会返回一个简单的字符串
OK。
- 这个命令的作用是:将列表

==阻塞操作==
阻塞操作是 Redis 列表的重要特性,常用于实现消息队列。
# 阻塞式左侧弹出
# 用法:BLPOP key [key ...] timeout
BLPOP task_queue 30 # 等待30秒,如果有元素则弹出
# 阻塞式右侧弹出
# 用法:BRPOP key [key ...] timeout
BRPOP task_queue 0 # 无限等待
# 阻塞式右侧弹出并左侧推入
# 用法:BRPOPLPUSH source destination timeout
BRPOPLPUSH pending_tasks processing_tasks 60
阻塞式左侧弹出:BLPOP
- 语法:
BLPOP key [key ...] timeout - 示例:
BLPOP task_queue 30 - 解释:
- 客户端向
task_queue列表请求数据。如果列表里有数据,立刻弹出一个(跟LPOP一样)。 - 重点:如果列表是空的,客户端会原地阻塞等待(也就是卡在这里)。等待最长 30 秒。如果在 30 秒内,有另一个客户端往
task_queue里RPUSH了一个新任务,这个阻塞的客户端就会瞬间被唤醒,拿到数据并返回。 - 如果超过 30 秒依然没数据,返回
(nil)。
- 客户端向
阻塞式右侧弹出:BRPOP
- 语法:
BRPOP key [key ...] timeout - 示例:
BRPOP task_queue 0 - 解释:
- 逻辑和
BLPOP完全一样,只是从右侧(尾部)弹出。 - 核心重点:
timeout的值如果是0,代表无限等待(永不超时)。除非有数据进来,或者网络断开/客户端主动断开,否则它会一直死死卡在这里等。(这在实战中常用来做常驻后台的消费者进程)。
- 逻辑和
高级进阶:==BRPOPLPUSH==(安全队列)
-
语法:
BRPOPLPUSH source destination timeout -
示例:
BRPOPLPUSH pending_tasks processing_tasks 60 -
解释:这个命令极其重要,它是“可靠消息队列”的基石。
- 它的动作是:阻塞等待
pending_tasks(源列表),一旦有数据,把它从右边弹出,同时立刻从左边推入processing_tasks(目标列表)。 - 为什么牛? 解决“消息丢失”问题。假如你只用
BRPOP拿到了任务,结果程序突然崩溃,这个任务就彻底丢了。用了BRPOPLPUSH,任务会被备份到processing_tasks里。等你处理完了,再把它删掉。如果崩了,重启后还可以从备份列表里捞出来重试。 - (注:Redis 6.2 之后,官方推出了更强大的
BLMOVE命令取代了它,但底层逻辑一致)。
- 它的动作是:阻塞等待
示例图理解阻塞
🎨 图 1:BLPOP(阻塞式左侧弹出)
场景:后台消费者死死盯着左边,来一个拿一个。
[ 初始状态 ] 列表为空,消费者开启阻塞监听:
Queue: [ 空 ]
客户端: BLPOP task_queue 30 ---> (💤 原地挂起,最多等30秒)
[ 3秒后 ] 生产者突然塞入新任务(RPUSH):
Queue: [ 任务A ]
^
|
客户端: (🔔 瞬间唤醒!) ---> 拿到 "任务A",命令结束返回。
[ 最终 ] 任务被取走:
Queue: [ 空 ]
- 核心机制:如果左边有数据立刻拿;没数据就原地干等,直到超时或来数据。
🎨 图 2:BRPOP(阻塞式右侧弹出 + timeout 0)
场景:常驻消费者无限等待,典型的 FIFO(先进先出)队列模式。
text
[ 初始状态 ] 消费者开启无限等待(0 代表死等):
Queue: [ 空 ]
客户端: BRPOP task_queue 0 ---> (🔒 彻底挂起,永不超时)
[ N小时后 ] 生产者用 LPUSH 从左边塞入:
Queue: [ 任务1, 任务2, 任务3 ]
^
|
客户端: (🔔 从右侧弹出!) ---> 拿到 "任务3"。
[ 最终 ] 按照先进先出的顺序,任务3最后进,最先被拿走。
Queue: [ 任务1, 任务2 ]
- 核心机制:
timeout = 0相当于把消费者进程“焊死”在这里,除非服务器宕机或网络断开,否则绝对不会返回 nil。
==🎨 图 3==:BRPOPLPUSH(安全转移:从右弹,推入左)
场景:==可靠队列。取任务的同时,把它备份到另一个列表里,防丢==。
text
[ 初始状态 ]
待处理列表: pending_tasks = [ 任务A, 任务B ]
处理中列表: processing_tasks = [ 空 ]
[ 执行命令 ]
客户端: BRPOPLPUSH pending_tasks processing_tasks 60
[ 一瞬间的原子操作 ] (任务从右出,立刻推入左边)
1. 从 pending_tasks 右侧弹出 "任务B"。
2. 立刻将 "任务B" 推入 processing_tasks 左侧。
[ 执行后结果 ]
待处理列表: pending_tasks = [ 任务A ] <-- 任务B没了
处理中列表: processing_tasks = [ 任务B ] <-- 任务B安全备份在这里!
- 核心机制:如果客户端拿到“任务B”后,还没处理完就突然断电了,没关系!重启后,系统可以去
processing_tasks里查,发现里面还有“任务B”,重新拿出来执行即可。这就是消息不丢失的基石。
列表的应用场景
消息队列实现
# 生产者:添加任务到队列
LPUSH task_queue "发送邮件:user@example.com"
LPUSH task_queue "生成报表:monthly_report"
LPUSH task_queue "数据备份:database_backup"
# 消费者:从队列获取任务
BRPOP task_queue 30
# 返回:1) "task_queue" 2) "发送邮件:user@example.com"
最新消息列表
# 添加新消息(保持最新100条)
LPUSH latest_news "新闻标题1"
LTRIM latest_news 0 99
# 获取最新10条消息
LRANGE latest_news 0 9 # 数据太多了保留部分消息
用户操作历史
# 记录用户操作
LPUSH user:1001:actions "登录:2024-01-15 10:30:00"
LPUSH user:1001:actions "查看商品:product_123"
LPUSH user:1001:actions "加入购物车:product_123"
# 获取最近操作历史
LRANGE user:1001:actions 0 9


集合 (Set)
集合的特点和应用
Redis 集合是字符串的无序集合,集合中的元素是唯一的。
主要特点:
- 唯一性:不允许重复元素
- 无序性:元素没有固定顺序
- 快速查找:O(1) 时间复杂度判断元素是否存在
- 集合运算:支持交集、并集、差集运算
常见应用场景:
- 标签系统
- 好友关系
- 权限管理
- 去重统计
- 抽奖系统
基本操作命令
添加和删除元素
# 添加元素
# 用法:SADD key member [member ...]
SADD tags "Redis" "数据库" "缓存" "NoSQL"
SADD user:1001:skills "Java" "Python" "Redis"
# 删除元素
# 用法:SREM key member [member ...]
SREM tags "缓存"
SREM user:1001:skills "Java"
# 随机删除并返回元素
# 用法:SPOP key [count]
SPOP tags # 随机删除一个元素
SPOP tags 2 # 随机删除两个元素
添加元素:SADD
- 语法:
SADD key member [member ...] - 代码示例 1:
SADD tags "Redis" "数据库" "缓存" "NoSQL"- 解释:向名为
tags的集合中添加 4 个元素。如果其中某个元素(比如 “Redis”)已经存在,Redis 会自动忽略它,不会报错。
- 解释:向名为
- 代码示例 2:
SADD user:1001:skills "Java" "Python" "Redis"- 解释:向名为
user:1001:skills的集合中添加用户的技能标签。 - 返回值:命令返回的是实际成功添加的元素个数(比如返回 4,说明 4 个都是新的;如果返回 2,说明有 2 个之前就已经在集合里了)。
- 解释:向名为
删除元素:SREM
- 语法:
SREM key member [member ...] - 代码示例:
SREM tags "缓存":从tags集合中精准删除"缓存"这个元素。SREM user:1001:skills "Java":从技能集合中移除"Java"。- 返回值:返回实际被成功删除的元素个数(如果删一个不存在的元素,返回 0)。
随机删除并返回:SPOP
这是 Set 最有趣、实战中最常用的命令之一,天然适合做“抽奖”或“随机分配”。
- 语法:
SPOP key [count] - 代码示例:
SPOP tags:从tags集合中随机挑 1 个元素,把它从集合里删掉,并把这个元素返回给你。SPOP tags 2:从tags集合中随机挑 2 个元素,删掉并返回(返回的是一个列表)。- ⚠️ 避坑提示:如果你填的
count大于集合里现有的元素总数(比如集合里只有 3 个,你填SPOP tags 5),Redis 不会报错,它会把剩下的 3 个全弹出来给你,然后集合就空了。
查询操作
# 获取所有元素
# 用法:SMEMBERS key
SMEMBERS tags
# 判断元素是否存在
# 用法:SISMEMBER key member
SISMEMBER tags "Redis" # 返回 1(存在)
SISMEMBER tags "MySQL" # 返回 0(不存在)
# 获取集合大小
# 用法:SCARD key
SCARD tags # 返回集合元素数量
# 随机获取元素(不删除)
# 用法:SRANDMEMBER key [count]
SRANDMEMBER tags # 随机返回一个元素
SRANDMEMBER tags 3 # 随机返回三个元素
获取所有元素:SMEMBERS
- 语法:
SMEMBERS key - 代码示例:
SMEMBERS tags - 详细解释:
- 返回集合
tags中包含的所有元素。 - ⚠️ 极客避坑:Set 是无序的,所以返回结果的顺序和你插入时的顺序完全不一样,这是正常现象。
- ⚠️ 性能警告:如果这个集合里有几十万个元素,执行这个命令会直接把 Redis 阻塞。生产环境严禁对大集合使用
SMEMBERS,如果非要遍历,请改用SSCAN命令分批次查。
- 返回集合
判断元素是否存在:SISMEMBER
- 语法:
SISMEMBER key member - 代码示例:
SISMEMBER tags "Redis"-> 返回1(代表存在/True)SISMEMBER tags "MySQL"-> 返回0(代表不存在/False)
- 详细解释:
- 判断某个特定的元素是否在集合中。
- 核心考点:这个命令的时间复杂度是 O(1)。底层是哈希表,查得极快。应用场景:判断“该用户是否点过赞”、“该IP是否在黑名单里”。
获取集合大小:SCARD
- 语法:
SCARD key - 代码示例:
SCARD tags - 详细解释:
- 返回集合中元素的个数(Cardinality 基数)。
- 时间复杂度也是 O(1)(底层自带计数器),哪怕集合里有 100 万个元素,也是瞬间返回。
- 应用场景:统计网站当天的独立访客数(UV)。
随机获取元素(不删除):SRANDMEMBER
- 语法:
SRANDMEMBER key [count] - 代码示例:
SRANDMEMBER tags-> 随机返回 1 个元素。SRANDMEMBER tags 3-> 随机返回 3 个元素。
- 详细解释:
- 注意和上一张图里的
SPOP区分:SRANDMEMBER只是随机“看一眼”,元素依然留在集合里,不会被删除。 - 进阶注意:如果你填的
count是正数(如 3),它保证返回的 3 个元素绝对不重复;如果你填的是负数(如-3),它返回的 3 个元素可能会有重复。
- 注意和上一张图里的
移动元素
# 将元素从一个集合移动到另一个集合
# 用法:SMOVE source destination member
SMOVE user:1001:skills user:1002:skills "Python"
移动元素:SMOVE
- 语法:
SMOVE source destination member - 代码示例:
SMOVE user:1001:skills user:1002:skills "Python" - 详细解释:
source(源集合):user:1001:skillsdestination(目标集合):user:1002:skillsmember(要移动的元素):"Python"- 执行动作:把
"Python"这个元素从用户 1001 的技能集合中删掉,同时添加到用户 1002 的技能集合中。
集合运算
Redis支持集合间的数学运算,这是集合类型的强大特性。
# 准备测试数据
SADD set1 "a" "b" "c" "d"
SADD set2 "c" "d" "e" "f"
SADD set3 "d" "e" "f" "g"
# 交集运算
# 用法:SINTER key [key ...]
SINTER set1 set2 # 返回 {"c", "d"}
SINTER set1 set2 set3 # 返回 {"d"}
# 并集运算
# 用法:SUNION key [key ...]
SUNION set1 set2 # 返回 {"a", "b", "c", "d", "e", "f"}
# 差集运算
# 用法:SDIFF key [key ...]
SDIFF set1 set2 # 返回 {"a", "b"}
SDIFF set2 set1 # 返回 {"e", "f"}
# 将运算结果存储到新集合
# 用法:[SINTERSTORE | SUNIONSTORE | SDIFFSTORE] destination key [key ...]
SINTERSTORE result set1 set2
SMEMBERS result # 查看交集结果
交集运算:SINTER(共同拥有的)
- 数学概念:两边都有的元素。
- 代码示例:
SINTER set1 set2-> 返回{"c", "d"}(set1 和 set2 共同有的)SINTER set1 set2 set3-> 返回{"d"}(三个集合都有的)
- 应用场景:共同好友、共同关注。比如
SINTER 用户A的关注 用户B的关注,瞬间找出他俩都关注了谁。
并集运算:SUNION(全部加起来的)
- 数学概念:把两边所有元素合并在一起(自动去重)。
- 代码示例:
SUNION set1 set2-> 返回{"a", "b", "c", "d", "e", "f"} - 应用场景:标签汇总、获取所有权限。比如把几个角色的权限集合合并,算出该用户的最终总权限。
差集运算:SDIFF(我独有的)
- 数学概念:在第一个集合里,但不包含在后面集合里的元素(有方向性)。
- 代码示例:
SDIFF set1 set2-> 返回{"a", "b"}(set1 有,但 set2 没有的)SDIFF set2 set1-> 返回{"e", "f"}(set2 有,但 set1 没有的)
- 应用场景:可能认识的人(比如:我的好友 – 我的好友的好友)、找茬(新上架的商品 – 已经看过的商品)。
存储结果:...STORE 系列(新集合存储)
带 STORE 后缀的命令。前面的 SINTER 只是在屏幕上打印结果,而加了 STORE 会在Redis 中生成一个新的集合来保存结果。
- 代码示例:
SINTERSTORE result set1 set2 - 详细解释:计算
set1和set2的交集,并把结果(c和d)存入一个名为result的新集合中。然后你用SMEMBERS result就能看到这个新集合的内容。 - (同样,SUNIONSTORE、SDIFFSTORE 也是这个逻辑)。
- ⚠️ 覆盖警告:如果
result这个键之前已经存在,它原有的数据会被直接清空覆盖。
有序集合 (zset)
有序集合的特点和应用
有序集合是 Redis 中最复杂的数据类型,它结合了集合和列表的特点。
主要特点:
- 唯一性:成员唯一,不允许重复
- 有序性:按分数(score)排序
- 双重索引:可以按成员或分数查找
- 范围查询:支持按分数或排名范围查询
常见应用场景:
- 排行榜系统
- 优先级队列
- 时间线排序
- 范围查询
- 权重计算
基本操作命令
添加和删除元素
# 添加元素(分数 成员)
# 用法:ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
ZADD leaderboard 1500 "玩家A"
ZADD leaderboard 2300 "玩家B" 1800 "玩家C" 2100 "玩家D"
# 批量添加
ZADD leaderboard 1200 "玩家E" 1900 "玩家F" 2500 "玩家G"
# 删除元素
# 用法:ZREM key member [member ...]
ZREM leaderboard "玩家E"
# 按分数范围删除
# 用法:ZREMRANGEBYSCORE key min max
ZREMRANGEBYSCORE leaderboard 0 1000
# 按排名范围删除
# 用法:ZREMRANGEBYRANK key start stop
ZREMRANGEBYRANK leaderboard 0 2 # 删除前3名
添加元素:ZADD
- 语法:
ZADD key [NX|XX] [CH] [INCR] score member [score member ...] - 代码示例:
ZADD leaderboard 1500 "玩家A"- 解释:往
leaderboard里添加“玩家A”,分数设为 1500。
- 解释:往
ZADD leaderboard 2300 "玩家B" 1800 "玩家C" 2100 "玩家D"- 解释:支持一次添加多个,格式为
分数1 成员1 分数2 成员2。
- 解释:支持一次添加多个,格式为
ZADD leaderboard 1200 "玩家E" 1900 "玩家F" 2500 "玩家G"- 解释:批量添加。如果成员已存在,ZADD 会更新该成员的分数。
- 💡 高级可选参数(括号里的):
NX:只添加不存在的成员(不更新已有分数)。XX:只更新已存在的成员(不添加新成员)。CH:返回值变为“发生变化的成员总数”(默认只返回新增的个数)。INCR:让分数像INCR命令一样递增(比如ZADD leaderboard INCR 100 "玩家A")。
删除元素:ZREM
- 语法:
ZREM key member [member ...] - 代码示例:
ZREM leaderboard "玩家E" - 解释:精准移除指定的成员(不管它的分数是多少)。返回实际删除的成员个数。
按分数范围删除:ZREMRANGEBYSCORE
- 语法:
ZREMRANGEBYSCORE key min max - 代码示例:
ZREMRANGEBYSCORE leaderboard 0 1000 - 解释:删除分数在
0到1000之间(包含边界)的所有成员。 - 应用场景:清理积分过低的“僵尸用户”。
按排名范围删除:ZREMRANGEBYRANK
- 语法:
ZREMRANGEBYRANK key start stop - 代码示例:
ZREMRANGEBYRANK leaderboard 0 2 - 解释:按名次删除。ZSet 的排名从
0开始,0 2代表删除排名第 1 到第 3 名的成员(前 3 名)。 - 应用场景:末位淘汰(删除排名最后 10 名可用
ZREMRANGEBYRANK key -10 -1)。
查询操作
# 获取元素数量
# 用法:ZCARD key
ZCARD leaderboard
# 获取元素分数
# 用法:ZSCORE key member
ZSCORE leaderboard "玩家B"
# 获取元素排名
# 用法:ZRANK key member # 正序排名(从0开始)
# 用法:ZREVRANK key member # 逆序排名(从0开始)
ZRANK leaderboard "玩家B"
ZREVRANK leaderboard "玩家B"
# 统计分数范围内的元素数量
# 用法:ZCOUNT key min max
ZCOUNT leaderboard 1500 2000
# 获取分数范围内的元素数量(字典序)
# 用法:ZLEXCOUNT key min max
ZLEXCOUNT leaderboard a c
获取元素数量:ZCARD
- 语法:
ZCARD key - 代码示例:
ZCARD leaderboard - 解释:返回集合中成员的总个数(Cardinality)。
获取元素分数:ZSCORE
- 语法:
ZSCORE key member - 代码示例:
ZSCORE leaderboard "玩家B" - 解释:查询指定成员的分数(score)。如果成员不存在,返回
(nil)。
获取元素排名:ZRANK / ZREVRANK(面试重点)
- 语法:
ZRANK key member/ZREVRANK key member - 代码示例:
ZRANK leaderboard "玩家B"-> 正序排名(分数从小到大,从 0 开始算)。ZREVRANK leaderboard "玩家B"-> 逆序排名(分数从大到小,从 0 开始算)。
- 解释:查询某个成员在当前集合中的名次。
- ⚠️ 避坑:返回值是从
0开始的。如果返回0,代表是第 1 名;如果返回(nil),说明该成员不在集合中。
统计分数范围内的元素数量:ZCOUNT
- 语法:
ZCOUNT key min max - 代码示例:
ZCOUNT leaderboard 1500 2000 - 解释:统计分数在
1500到2000(包含边界)之间的成员个数。 - 应用场景:例如统计“考试成绩在 80-100 分之间的学生人数”、“VIP 积分在 1000 分以上的用户数”。
范围操作
有序集合的范围操作是其最强大的功能之一。
按排名范围查询
# 按排名获取元素(正序)
# 用法:ZRANGE key start stop [WITHSCORES]
ZRANGE leaderboard 0 2 # 获取前3名
ZRANGE leaderboard 0 2 WITHSCORES # 获取前3名及分数
ZRANGE leaderboard 0 -1 # 获取所有元素
# 按排名获取元素(逆序)
# 用法:ZREVRANGE key start stop [WITHSCORES]
ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前3名(高分到低分)
1. 按“排名”范围查询(看名次)
正序查:ZRANGE(分数从低到高)
- 语法:
ZRANGE key start stop [WITHSCORES] - 代码示例:
ZRANGE leaderboard 0 2:取排名第 1 到第 3 的成员(索引 0, 1, 2)。ZRANGE leaderboard 0 2 WITHSCORES:取前 3 名,并且带出他们的分数(加上WITHSCORES返回值会成对出现:成员1, 分数1, 成员2, 分数2…)。ZRANGE leaderboard 0 -1:取所有成员(-1代表最后一个)。
逆序查:ZREVRANGE(分数从高到低)
- 语法:
ZREVRANGE key start stop [WITHSCORES] - 代码示例:
ZREVRANGE leaderboard 0 2 WITHSCORES - 解释:取分数最高的前 3 名及其分数。做排行榜(如游戏榜单、热搜榜)通常用这个。
按分数范围查询
# 按分数范围获取元素(正序)
# 用法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANGEBYSCORE leaderboard 1500 2000
ZRANGEBYSCORE leaderboard 1500 2000 WITHSCORES
ZRANGEBYSCORE leaderboard 1500 2000 LIMIT 0 5
# 按分数范围获取元素(逆序)
# 用法:ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
ZREVRANGEBYSCORE leaderboard 2000 1500 WITHSCORES
# 使用开区间
ZRANGEBYSCORE leaderboard 1500 2000 # 不包含1500和2000
ZRANGEBYSCORE leaderboard -inf +inf # 所有元素
正序查询:ZRANGEBYSCORE
- 语法:
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] - 核心逻辑:查找分数在
min到max之间的成员,按分数从低到高(正序)排列。 - 代码拆解:
ZRANGEBYSCORE leaderboard 1500 2000- 解释:获取分数在 1500 到 2000(包含边界)之间的所有成员。
ZRANGEBYSCORE leaderboard 1500 2000 WITHSCORES- 解释:同上,但在返回成员名字的同时,附带返回他们的分数。
ZRANGEBYSCORE leaderboard 1500 2000 LIMIT 0 5- 解释:分页查询。在 1500 到 2000 分之间,跳过
offset 0个,取出count 5个(即取前 5 名)。
- 解释:分页查询。在 1500 到 2000 分之间,跳过
逆序查询:ZREVRANGEBYSCORE
- 语法:
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] - 核心逻辑:按分数从高到低(逆序)排列。
- ⚠️ 极其重要的避坑点:因为是从高到低排,所以参数顺序变了!
max必须写在前面,min写在后面。 - 代码拆解:
ZREVRANGEBYSCORE leaderboard 2000 1500 WITHSCORES- 解释:获取分数在 2000 到 1500 之间的成员,按分数从高到低返回。如果你写成
1500 2000,将返回空结果。
按字典序范围查询
# 按字典序范围获取元素
# 用法:ZRANGEBYLEX key min max [LIMIT offset count]
ZRANGEBYLEX leaderboard a c
# 用法:ZREVRANGEBYLEX key max min [LIMIT offset count]
ZREVRANGEBYLEX leaderboard c a
# 示例:相同分数的元素按字典序排列
ZADD words 0 "apple" 0 "banana" 0 "cherry" 0 "date"
ZRANGEBYLEX words a c # 获取a到c之间的单词
ZRANGEBYLEX words banana date # 获取banana到date之间的单词
正序查询:ZRANGEBYLEX
- 语法:
ZRANGEBYLEX key min max [LIMIT offset count] - 代码示例:
ZRANGEBYLEX leaderboard a c - 解释:在
leaderboard集合中,找出成员名字在字母a到c之间(包含a和c)的所有元素,按从 A 到 Z 的正序排列。
逆序查询:ZREVRANGEBYLEX
- 语法:
ZREVRANGEBYLEX key max min [LIMIT offset count] - 代码示例:
ZREVRANGEBYLEX leaderboard c a - ⚠️ 避坑警告:和按分数逆序查一样,参数顺序必须反过来! 最大值
c写在前面,最小值a写在后面,代表从 Z 到 A 的逆序排列。
- 解释:你向
words集合中添加了 4 个单词,注意看,它们的分数全部是0。在 Redis 底层,它们会自动按字母顺序排好:apple->banana->cherry->date。
接着执行:
ZRANGEBYLEX words a c-> 返回apple(以a开头)和banana(以b开头)。因为cherry是以c开头的,其实也包含在内。ZRANGEBYLEX words banana date-> 返回从banana到date之间的单词(包含banana和date)。