Redis 的核心优势之一就是丰富且高效的数据结构(数据存储类型)。相比传统数据库只存表结构,Redis 更像是一个"数据结构服务器"。
Redis 目前主流有 7 大核心数据类型:
|-------------|------|-----------|
| 类型 | 名称 | 说明 |
| string | 字符串 | 最基础类型(万能) |
| list | 列表 | 有序、可重复 |
| set | 集合 | 无序、不可重复 |
| zset | 有序集合 | 有序 + 分数 |
| hash | 哈希 | 类似对象 |
| bitmap | 位图 | 位级别操作 |
| hyperloglog | 基数统计 | 去重计数(近似) |
| stream | 流 | 消息队列(高级) |
一,string 字符串数据类型
string 字符串数据类型可以通过 SET 命令来进行设置存储,SET 的命令如下:
cs
SET key value [NX | XX | IFEQ ifeq-value | IFNE ifne-value |
IFDEQ ifdeq-digest | IFDNE ifdne-digest] [GET] [EX seconds |
PX milliseconds | EXAT unix-time-seconds |
PXAT unix-time-milliseconds | KEEPTTL]
# 时间复杂度
后面的参数小编一个个的来解析与讲解
1.1 SET 简单使用
【redis-cli】SET key value 直接对指定的key赋值
bash
127.0.0.1:6379> SET username toast
OK
127.0.0.1:6379> GET username
"toast"
SET key value key如果存在就是直接覆盖旧值,key 如果不存在则新建key在赋值
【redis-cli】SET key value GET 在 SET 命令赋值的基础上获取被覆盖的旧值
cpp
127.0.0.1:6379> SET age 24 GET
(nil)
127.0.0.1:6379> SET age 27 GET
"24"
127.0.0.1:6379>
1.2 MSET 设置多个key-value
cpp
127.0.0.1:6379> MSET toast-java java toast-python Python toast-go Golang
OK
127.0.0.1:6379> GET toast-java
"java"
1.3 SET 过期时间控制
SET 秒级过期时间 SET key value EX seconds
cs
SET key value EX 60 # 表示该值 60 秒过期失效
SET 毫秒级过期时间 SET key value PX milliseconds
cs
SET key value PX 1000 # 表示该值 1000 毫秒过期
如果是直接设置的话,是没有过期时间的
cs
SET key value
像这种直接设置的话,没有过期时间,后期需要引入过期时间的话。就直接在重新执行一次追加有过期参数的命令即可。
如果你想在改值的时候,同时还需要保留过期时间,可以使用另外一个命令: KEEPTTL
从 命令名称 KEEPTTL 也可以看出是用于保留当前的过期时间的。
案例如下:
cs
127.0.0.1:6379> SET user:100 hello EX 60 # 设置一个60秒过期的值
OK
127.0.0.1:6379> TTL user:100 # 查看过期时间,还有 52 秒
(integer) 52
127.0.0.1:6379> SET user:100 world KEEPTTL # 更新值并保留当前过期时间
OK
127.0.0.1:6379> TTL user:100 # 查看过期时间,还有 41 秒
(integer) 41
127.0.0.1:6379> SET user:100 world2 # 直接更新,覆盖过期时间,此时永久有效
OK
127.0.0.1:6379> TTL user:100 # 查看过期时间, -1 表示永久有效
(integer) -1
127.0.0.1:6379> SET user:100 hello EX 60 # 重新设置
1.4 SET 时间戳过期时间控制
EXAT
cs
SET key value EXAT 1717320000
👉 表示:在 某个时间点(秒级时间戳)过期
PXAT
cs
SET key value PXAT 1717320000000
👉毫秒级时间戳
👉 使用场景:
- 定时任务
- 活动结束时间
- Token 到期(统一时间)
1.5 SET 条件判断写入
✔ 只在key 不存在 时写入(分布式锁核心) NX = N ot eXists
cs
SET key value NX
👉 等价于:
cs
SETNX key value # 这个是老版的命令
只在 key 存在时写入 👉 用于更新操作 XX = eXists ×2(双 X 强调 Exist)
cs
SET key value XX
SET NX 做锁:标准分布式锁
cs
SET lock:order:1001 1 NX EX 30
逻辑:lock:order:1001 不存在 → 加锁成功;已存在 → 抢锁失败。
场景:全局互斥锁(下单、任务执行、接口防重、资源独占)。
特点:一把锁 = 一个独立 String Key。
1.6 SET 提供的乐观锁
IFEQ ( If Equal )
cpp
SET key newValue IFEQ oldValue
含义:只有当当前值 == oldValue ,才更新 本质就是 CAS(Compare And Set)
cs
SET counter 10
SET counter 20 IFEQ 10 # 成功
SET counter 30 IFEQ 10 # 失败
IFNE(If Not Equal)
SET key value IFNE someValue
👉 含义: 当前值 ≠ 指定值 才更新
IFDEQ / IFDNE(更底层:基于 digest)
👉 这个是更底层版本,用的是 value 的 hash/digest 比较
IFDEQ # digest equal
IFDNE # digest not equal
用途
避免大 value 比较开销 ,
内部优化 / 高性能场景
1.7 组合使用
一个场景案例:key 不存在才设置 30 秒自动过期 这个也是分布式锁标准写法
cs
SET lock_key uuid NX EX 30
1.8 SET 的特征
SET 命令是有原子性特征的,要么成功,要么失败,不会出现并发问题
SET 命令是有写入覆盖的特点
cs
SET key v1
SET key v2
# 最终
key = v2
SET 命令,如果 key 不存在则自动创建
1.9 总结
原子操作 :整个命令要么全部执行成功,要么全部不执行,无中间状态,并发安全
覆盖默认行为 :不指定任何参数时,SET key value 会直接覆盖已有 key
功能大一统:替代了老版 SETNX/ SETEX/ PSETEX等命令,一条命令搞定所有场景
支持 CAS 乐观锁:Redis 6.2+ 新增条件判断,实现值比对 + 赋值原子操作
参数分类:
【过期控制参数】: EX, PX/EXAT, PXAT, KEEPTTL
【值条件】: NX, XX
【CAS】: IFEQ, IFNE, IFDEQ, IFDNE
IFEQ, IFNE, IFDEQ, IFDNE 是 Redis 8.4 新版本才出的原子CAS参数。
二,KEYS & TYPE 命令
2.1 KEYS
|----|------------------|-----------|------|
| 命令 | KEYS pattern | 时间复杂度 | O(N) |
| 描述 | 处理大型数据库时使用该命令可能会严重影响性能 |||
php
pattern 支持的匹配模式:
? 单个字符匹配: h?llo 与 hello 、 hallo 和 hxllo 相匹配。
* 任意字符匹配: h*llo 与 hllo 以及 heeeello 相匹配。
[字符] 指定字符匹配: h[ae]llo 与 hello 和 hallo, 相匹配,但不与 hillo 相匹配。
[^字符] 排除字符匹配: h[^e]llo 与 hallo 、 hbllo 等相匹配,但不与 hello 相匹配。
[-] 字符范围匹配: h[a-b]llo 与 hallo 以及 hbllo 相匹配。
在实际开发之中,一般为了性能 keys 命令公司一般都是禁用,因为 Redis 往往会保存海量的数据,那么海量的数据直接查询,会严重影响到 Redis 性能。
cs
127.0.0.1:6379> MSET toast-java java toast-python Python toast-go Golang
OK
127.0.0.1:6379> KEYS toast-java
1) "toast-java"
KEYS 也支持模糊查询,可以在内容的前置或后置追加 * 号表示模糊查询
cs
127.0.0.1:6379> KEYS toast*
1) "toast-go"
2) "toast-java"
3) "toast-python"
2.2 TYPE
|----|--------------|-----------|------|
| 命令 | TYPE key | 时间复杂度 | O(1) |
| 描述 | 返回存储在 key 处的值的类型对应的字符串表示形式。可能返回的类型包括: string 、 list 、 set 、 zset 、 hash 、 stream 和 vectorset |||
TYPE 命令是用于查询 key的属性类信息
cs
127.0.0.1:6379> KEYS *
1) "addr"
2) "toast-go"
3) "age"
4) "member:toast"
5) "username"
6) "usernmae"
7) "toast-java"
8) "toast-python"
127.0.0.1:6379> TYPE member:toast
hash
127.0.0.1:6379> TYPE age
string
127.0.0.1:6379> TYPE addr
string
三,Hash 数据
如果说现在要通过 Redis 描述一个完整的对象信息(一个对象会有多个属性的内容),按照传统的做法此时需要创建有多个数据KEY。如下所示:
cs
SET member:toast:name zhangsan
SET member:toast:age 16
SET member:toast:job Teacher
如果此时需要保存不用用户的信息,只需要修改用户名即可,但是随之而来新的问题产生了,一个完整的对象信息要通过三个数据项来进行描述,那么一旦要存放有 100 W 条的数据,那么就需要提供有 300W 个数据项,每一次数据获取的时候都需要进行哈希的定位,所以最佳的做法并不是进行拆分,而是要将这多个不同的数据项捆绑在一个 key 上,所以才有了哈希结构。
3.1 HSET 命令
|----|----------------------------------------------|-----------|------|
| 命令 | HSET key field value field value ... | 时间复杂度 | O(1) |
| 描述 | 将指定的字段值设置为存储在 key 中的哈希表中对应的数值 |||
Redis 里面的 Hash 数据类型,可以理解成 Redis 里面的 JSON 数据结构。可以存储一个对象。案例如下:
cs
HSET member:toast name zhangsan age 16 job Teacher
这样一个key 存储了一个对象,而不是三个key存储一个对象,这样节省存储空间
3.2 HGET 命令
|----|--------------------|-----------|------|
| 命令 | HGET key field | 时间复杂度 | O(1) |
| 描述 | 返回存储在 key 中的哈希表中、与 field 相关联的值。 |||
HGET 就是用于获取 HSET 设置的属性,命令如下:
cs
127.0.0.1:6379> HSET member:toast name zhangsan age 16 job Teacher
(integer) 3
127.0.0.1:6379> HGET member:toast name
"zhangsan"
127.0.0.1:6379>
3.3 HGETALL 获取全部字段命令
|----|-----------------|-----------|------|
| 命令 | HGETALL key | 时间复杂度 | O(N) |
| 描述 | 返回存储在 key 处的哈希表中的所有字段和值。在返回的值中,每个字段名后面都跟着该字段的值。 |||
HGETALL 用于获取全部的 HGETALL 命令
cs
127.0.0.1:6379> HGETALL member:toast
1) "name"
2) "zhangsan"
3) "age"
4) "16"
5) "job"
6) "Teacher"
127.0.0.1:6379>
3.4 HMGET → 批量取多个字段
|----|-----------------|-----------|------|
| 命令 | HGETALL key | 时间复杂度 | O(N) |
| 描述 | 返回存储在 key 中的哈希表中、与指定键 fields 相关联的值。 |||
cs
127.0.0.1:6379> HMGET member:toast name age
1) "zhangsan"
2) "16"
127.0.0.1:6379>
3.5 HKEYS 获取所有 Hash 属性的 key 列表
|----|---------------|-----------|------|
| 命令 | HKEYS key | 时间复杂度 | O(N) |
| 描述 | 返回存储在 key 中的哈希表中所有的字段名称。 |||
cs
127.0.0.1:6379> HSET member:toast name one age 23
(integer) 0
127.0.0.1:6379> HSET member:toast name two age 24
(integer) 0
127.0.0.1:6379> HKEYS member:toast
1) "name"
2) "age"
3.6 HLEN 获取指定 key 保存的对象个数
|----|--------------|-----------|------|
| 命令 | HLEN key | 时间复杂度 | O(N) |
| 描述 | 返回存储在 key 处的哈希表中包含的字段数量。 |||
cs
127.0.0.1:6379> HSET member:toast name one age 23
(integer) 0
127.0.0.1:6379> HSET member:toast name two age 24
(integer) 0
127.0.0.1:6379> HLEN member:toast
(integer) 2
3.7 HDEL → 删除某个字段
|----|----------------------------------|-----------|------|
| 命令 | HDEL key field field ... | 时间复杂度 | O(N) |
| 描述 | 从存储在 key 处的哈希表中删除指定的字段。如果该哈希表中不存在这些字段,则会忽略它们。 |||
cs
127.0.0.1:6379> HDEL member:toast job # 删除 job 字段属性和值
(integer) 1
127.0.0.1:6379> HGETALL member:toast
1) "name"
2) "zhangsan"
3) "age"
4) "16"
127.0.0.1:6379>
3.8 HVALS 获取指定key 的对象值
|----|---------------|-----------|------|
| 命令 | HVALS key | 时间复杂度 | O(N) |
| 描述 | 返回存储在 key 中的哈希表中的所有值。 |||
cs
127.0.0.1:6379> HSET member:toast name one age 23
(integer) 0
127.0.0.1:6379> HSET member:toast name two age 24
(integer) 0
127.0.0.1:6379> HVALS member:toast
1) "two"
2) "24"
3.9 HEXISTS → 判断指定key的属性字段是否存在(1 - 存在 | 0 - 不存在)
|----|-----------------------|-----------|------|
| 命令 | HEXISTS key field | 时间复杂度 | O(1) |
| 描述 | 如果 field 是存储在 key 中的哈希表中已存在的字段,则返回 1。否则为 0 |||
cs
127.0.0.1:6379> HSET member:toast name one age 23
(integer) 0
127.0.0.1:6379> HSET member:toast name two age 24
(integer) 0
127.0.0.1:6379> HEXISTS member:toast name
(integer) 1
127.0.0.1:6379> HEXISTS member:toast addr
(integer) 0
四,数字操作
在实际的开发之中一般会出现一种场景。在数据库之中有一个数字,而这个数字在高并发访问下的数据更新问题(自增操作);
redis 在设计的时候考虑到此类的需要,提供了数据的修改支持,可以直接通过命令实现数据修改操作。
4.1 INCR key
|----|--------------|-----------|------|
| 命令 | INCR key | 时间复杂度 | O(1) |
| 描述 | 将存储在 key 处的数值自增加 1。如果该键不存在,则在执行该操作之前,会先将其设置为 0 。 |||
对指定key的数据进行自增,如果数就不存在则新建
cs
127.0.0.1:6379> INCR visits
(integer) 1
127.0.0.1:6379> INCR visits
(integer) 2
127.0.0.1:6379> INCR visits
(integer) 3
127.0.0.1:6379> INCR visits
(integer) 4
127.0.0.1:6379> INCR visits
(integer) 5
127.0.0.1:6379> GET visits
"5"
4.2 DECR key
|----|--------------|-----------|------|
| 命令 | DECR key | 时间复杂度 | O(1) |
| 描述 | 将存储在 key 处的数值减 1。如果该键不存在,则在执行该操作之前,会先将其设置为 0 。 |||
对指定key的数据进行自减,如果数据不存在则新建
cs
# 通过 TYPE 命令得知,datas key 不存在
127.0.0.1:6379> TYPE datas
none
# key 不存在的时候,会先设置为 0 ,在减一
127.0.0.1:6379> DECR datas
(integer) -1
127.0.0.1:6379> DECR datas
(integer) -2
4.3 INCRBY key 数值
|----|--------------------------|-----------|------|
| 命令 | INCRBY key increment | 时间复杂度 | O(1) |
| 描述 | 将存储在 key 处的数值自增指定步长(increment )。如果该键不存在,则在执行该操作之前,会先将其设置为 0 。 |||
对指定key的数据进行指定步长数值追加,如果key不存在则新建
cs
127.0.0.1:6379> INCRBY total 110
(integer) 110
127.0.0.1:6379> INCRBY total 110
(integer) 220
4.4 DECRBY key 数值
|----|--------------------------|-----------|------|
| 命令 | DECRBY key decrement | 时间复杂度 | O(1) |
| 描述 | 将存储在 key 处的数值自减指定步长 (increment )。如果该键不存在,则在执行该操作之前,会先将其设置为 0 。 |||
对指定key的数据进行指定步长数值减少,如果key不存在则新增
cs
# 通过 TYPE 命令得知,total key 不存在
127.0.0.1:6379> TYPE total
none
# key 不存在的时候,会先设置为 0 ,在减指定步长 100
127.0.0.1:6379> DECRBY total 100
(integer) -100
127.0.0.1:6379> DECRBY total 1
(integer) -101
4.5 INCRBYFLOAT 浮点数自增
|----|-------------------------------|-----------|------|
| 命令 | INCRBYFLOAT key increment | 时间复杂度 | O(1) |
| 描述 | 将存储在 key 处的浮点数所对应的字符串值增加指定的 increment 数值。如果使用负数的 increment 值,那么存储在该键处的数值将会减少(这是加法的本质属性所决定的)。 |||
cs
127.0.0.1:6379> INCRBYFLOAT number 100.01
"100.01"
127.0.0.1:6379> INCRBYFLOAT number 1.01
"101.02"
127.0.0.1:6379> INCRBYFLOAT number 0.21
"101.23"
127.0.0.1:6379> INCRBYFLOAT number -0.51
"100.72"
4.6 Hash 数据类型的自增自减
Redis 对 Hash 数据类型专门用于数字自增,自减的命令只有 2 个 ,一个是用于 整数形 的自增和自减,一个用于 浮点数 的自增和自减
HINCRBY ------ 整数 自增 / 自减
cs
127.0.0.1:6379> HINCRBY product:100 stock 5 # 自增 +5
(integer) 5
127.0.0.1:6379> HINCRBY product:100 stock -3 # 自减 -3 (写成负数就是自减)
(integer) 2
HINCRBYFLOAT ------ 整数 自增 / 自减
cs
127.0.0.1:6379> HINCRBYFLOAT order:100 price 1.5
"1.5"
127.0.0.1:6379> HINCRBYFLOAT order:100 price -0.8
"0.7"
HINCRBY / HINCRBYFLOAT 同样是原子操作,并发安全 。key不存在时从 0 开始记录。
同理 INCR, INCRBY, DECR, DECRBY 也是具有原子操作,并发按照。 不管多少线程、多少服务、多少并发同时执行,结果永远正确,不会出现数值错乱。
五,List 队列
Redis 里面的 List 数据结构,就是一个双向队列,并且支持高并发。对新增的元素是有序的,并且可以新增重复的元素数据。所以本质上List是一个双向,有序,可重复的字符串列队。
有序性:有序的体现就是依据插入的顺序进行排队
重复性:可以插入重复的字符串元素
双向性:可左进左出,左进右出,右进右出,右进左出。可以两头操作:左边 LPUSH、右边 RPUSH
常用作:消息队列、任务队列、排行榜、历史记录
Redis 的 List 队列是一个高性能的队列, 可以左进右出/右进左出(队列)、左进左出/右进右出(栈)
5.1 左进左出 LPUSH + LPOP → 栈 (先进后出 LIFO)
|----|------------------------------------------------------|
| 命令 | LPUSH key element element ... |
| 描述 | 往 key 队列左边追加元素,从Redis2.4.0 开始可以支持追加多个元素,元素依次往左排序 |
|----|--------------------------------------------------------------------------------------------------------------------------|
| 命令 | LPOP key count |
| 描述 | 移除并返回存储在 key 中的列表中的第一个元素(左边的元素) 默认情况下,该命令会从列表的左边开头选取一个元素。如果提供了可选的 count 参数,那么回复将包含最多 count 个元素,具体数量取决于列表的长度。 |
cs
127.0.0.1:6379> LPUSH stack left-A # 左进 left-A
(integer) 1
127.0.0.1:6379> LPUSH stack left-B # 左进 left-B
(integer) 2
127.0.0.1:6379> LPUSH stack left-C # 左进 left-C
(integer) 3
# 现在队列: 往左边逐个追加元素 ---> left-C left-B left-A
127.0.0.1:6379> LPOP stack # 左弹 left-C
"left-C"
127.0.0.1:6379> LPOP stack # 左弹 left-B
"left-B"
127.0.0.1:6379> LPOP stack # 左弹 left-A
"left-A"
cs
空队列:[ <=====> ]
LPUSH A → [A <=====> ]
LPUSH B → [B A <=====> ]
LPUSH C → [C B A <=====> ]
LPOP → 左端取出C → [B A <=====> ]
LPOP → 左端取出B → [A <=====> ]
LPOP → 左端取出A → [ <=====> ]
5.2 右进右出 RPUSH + RPOP → 栈 (先进后出 LIFO)
|----|------------------------------------------------------|
| 命令 | RPUSH key element element ... |
| 描述 | 往 key 队列右边追加元素,从Redis2.4.0 开始可以支持追加多个元素,元素依次往右排序 |
|----|---------------------------------------------------------------------------------------------------------------------------|
| 命令 | RPOP key count |
| 描述 | 移除并返回存储在 key 中的列表中的最后一个元素(右边的元素) 默认情况下,该命令会从列表的(右边)最末尾的元素。如果提供了可选的 count 参数,那么回复将包含最多 count 个元素,具体数量取决于列表的长度。 |
cs
127.0.0.1:6379> RPUSH stack right-A
(integer) 1
127.0.0.1:6379> RPUSH stack right-B
(integer) 2
127.0.0.1:6379> RPUSH stack right-C
(integer) 3
# 现在队列: left-A left-B left-C <--- 往右边逐个追加元素
127.0.0.1:6379> RPOP stack # 往右边弹出
"right-C"
127.0.0.1:6379> RPOP stack
"right-B"
127.0.0.1:6379> RPOP stack
"right-A"
cs
空队列:[ <=====> ]
RPUSH A → [ <=====> A]
RPUSH B → [ <=====> A B]
RPUSH C → [ <=====> A B C]
RPOP → 右端取出C → [ <=====> A B]
RPOP → 右端取出B → [ <=====> A]
RPOP → 右端取出A → [ <=====> ]
5.3 左进右出 LPUSH + RPOP → 标准队列 (先进先出 FIFO)
cs
127.0.0.1:6379> LPUSH queue A
(integer) 1
127.0.0.1:6379> LPUSH queue B
(integer) 2
127.0.0.1:6379> LPUSH queue C
(integer) 3
# 现在队列: 往左边逐个追加元素 ---> C B A
# 往右边弹出,顺序就是:A, B, C
127.0.0.1:6379> RPOP queue
"A"
127.0.0.1:6379> RPOP queue
"B"
127.0.0.1:6379> RPOP queue
"C"
cpp
空队列:[ <=====> ]
LPUSH A → [A <=====> ]
LPUSH B → [B A <=====> ]
LPUSH C → [C B A <=====> ]
RPOP → 右端取出A → [C B <=====> ]
RPOP → 右端取出B → [C <=====> ]
RPOP → 右端取出C → [ <=====> ]
5.4 右进左出|RPUSH + LPOP → 标准队列 (先进先出 FIFO)
cs
127.0.0.1:6379> RPUSH msg_queue A
(integer) 1
127.0.0.1:6379> RPUSH msg_queue B
(integer) 2
127.0.0.1:6379> RPUSH msg_queue C
(integer) 3
# 现在队列: A B C <--- 往右边逐个追加元素
# 往左边弹出,顺序如下: A, B, C
127.0.0.1:6379> LPOP msg_queue
"A"
127.0.0.1:6379> LPOP msg_queue
"B"
127.0.0.1:6379> LPOP msg_queue
"C"
示意图
空队列:[ <=====> ]
RPUSH A → [ <=====> A]
RPUSH B → [ <=====> A B]
RPUSH C → [ <=====> A B C]
LPOP → 左端取出A → [ <=====> B C]
LPOP → 左端取出B → [ <=====> C]
LPOP → 左端取出C → [ <=====> ]
5.5 LRANGE -- 查看 List 队列
|----|---------------------------|-----------|-------------------------|
| 命令 | LRANGE key start stop | 时间复杂度 | O(S+N),S 起始偏移量到HEAD 的距离 |
| 描述 | 返回存储在 key 位置的列表中的指定元素。 start 和 stop 是基于 0 的索引 |||
LRANGE 是 Redis List 列表专用的查询命令 ,只查看、不删除、不修改队列数据,专门用来打印 / 查看 List 里的元素。
cs
127.0.0.1:6379> RPUSH mylist "one"
(integer) 1
127.0.0.1:6379> RPUSH mylist "two"
(integer) 2
127.0.0.1:6379> RPUSH mylist "three"
(integer) 3
# 查看队列里 所有元素(工作99%用这个)
127.0.0.1:6379> LRANGE mylist 0 -1
1) "one"
2) "two"
3) "three"
LRANGE 各种查询
cs
LRANGE mylist 0 2 # 取前3个:A B C
LRANGE mylist 0 -1 # 取全部:A B C D E
LRANGE mylist -2 -1 # 取最后2个:D E
LRANGE mylist 1 3 # 取第2~第4个:B C D
5.6 真实场景案例: 异步短信发送队列(生产环境经典 List 使用)
场景说明
业务下单 / 注册后不阻塞主线程,手机号丢入 Redis List 队列;独立消费者进程阻塞轮询发送短信。 采用:RPUSH (右端生产) + BLPOP (左端阻塞消费) = 右进左出 FIFO
1. 生产者(业务代码写入)
cs
# 用户注册,写入待发短信
RPUSH sms_queue "13800001111|您已注册成功"
RPUSH sms_queue "13900002222|订单已发货"
RPUSH sms_queue "13700003333|优惠券到账"
2. 消费者(后台守护进程阻塞消费,不会空轮询耗 CPU)
cs
# 阻塞等待,超时30s,无消息阻塞休眠
BLPOP sms_queue 30
业务逻辑
取出手机号 + 文案 → 调用第三方短信接口发送 → 发送失败可重新 RPUSH 回队列重试。
补充:整队列过期管控
cs
EXPIRE sms_queue 86400 # 队列24h过期,防止堆积脏数据
六,Set 数据类型
Redis Set(集合) 是一个无序、不可重复 的字符串集合,底层通过哈希表实现,增删改查、判断成员是否存在的时间复杂度都是 O (1),非常高效。
Set 数据类型的特点如下
无序性:元素没有固定顺序,每次获取顺序可能不同
唯一性:自动去重,同一个值只能存一次
元素类型:只能存字符串,不能嵌套其他数据结构
集合操作 :支持交集、并集、差集等高级运算(核心优势)
Set 集合的核心优势是处理交集,并集,差集,也就是集合与集合之间的运算。
6.1 SADD
|----|------------------------------------|-----------|---------------|
| 命令 | SADD key member member ... | 时间复杂度 | O(N) N 表示元素个数 |
| 描述 | 将指定的成员添加到存储在 key 中的集合中。 如果这些成员已经属于该集合,那么它们将被忽略。 如果 key 不存在,那么在添加指定成员之前会先创建一个新的集合。 |||
cs
127.0.0.1:6379> SADD skills search-tools map-index-tools openclaw-tools
(integer) 3
127.0.0.1:6379> SADD skills search-tools openclaw-tools
(integer) 0
返回的数字表示新增到集合 skills 的个数,如果是有重复将忽略不添加。
6.2 SMEMBERS
|----|------------------|-----------|--------------------|
| 命令 | SMEMBERS key | 时间复杂度 | O(N),其中 N 表示集合的基数。 |
| 描述 | 获取指定 key 集合中的全部数据 |||
cs
127.0.0.1:6379> SMEMBERS skills
1) "search-tools"
2) "map-index-tools"
3) "openclaw-tools"
6.3 SREM
|----|------------------------------------|-----------|-------------------|
| 命令 | SREM key member member ... | 时间复杂度 | O(N),N 是需要移除的成员数量 |
| 描述 | 从存储在 key 中的集合中移除指定的成员。不属于该集合的成员将被忽略。如果 key 不存在,则将其视为空集合,此命令会返回 0 的值。 |||
css
127.0.0.1:6379> SREM skills openclaw-tools hello-tools
(integer) 1
127.0.0.1:6379> SMEMBERS skills
1) "search-tools"
2) "map-index-tools"
场景案例-文章标签标案
cs
# 给文章1001加标签
127.0.0.1:6379> SADD article:1001:tags Redis 后端 缓存 高并发
(integer) 4
# 给文章1002加标签
127.0.0.1:6379> SADD article:1002:tags MySQL 数据库 后端
(integer) 3
# 查看文章1001标签
127.0.0.1:6379> SMEMBERS article:1001:tags
Redis
后端
缓存
高并发
# 统计文章标签个数
127.0.0.1:6379> SCARD article:1001:tags
4
# 删除文章标签
127.0.0.1:6379> SREM article:1001:tags 高并发
6.4 SPOP
|----|------------------------|-----------|-------------------|
| 命令 | SPOP key count | 时间复杂度 | O(N),N 是需要弹出的成员数量 |
| 描述 | 默认情况下,该命令会从集合中取出一个成员。如果提供了可选的 count 参数,那么回复中将会包含最多 count 个成员,具体数量取决于集合的基数。 |||
cs
127.0.0.1:6379> SPOP skills
"search-tools"
127.0.0.1:6379> SPOP skills 10 # 超过集合里的元素时,会全部弹出
1) "map-index-tools"
场景案例-抽奖活动:随机不重复中奖(SPOP)
cs
# 添加抽奖名单
127.0.0.1:6379> SADD lottery:2026 zhangsan lisi wangwu zhaoliu zhouqi
(integer) 5
# 随机抽 1 人
127.0.0.1:6379> SPOP lottery:2026
"zhangsan"
# 随机抽 3 人
127.0.0.1:6379> SPOP lottery:2026 3
1) "lisi"
2) "wangwu"
3) "zhaoliu"
6.5 SDIFF 差集
|----|---------------------------|-----------|--------------------------|
| 命令 | SDIFF key key ... | 时间复杂度 | O(N),其中 N 表示所有给定集合中的元素总数 |
| 描述 | 返回从第一个集合与所有后续集合的差集所得到的集合中的元素。 |||
cs
127.0.0.1:6379> SADD S1 tools-A tools-B tools-C tools-D
(integer) 4
127.0.0.1:6379> SADD S2 tools-B tools-C tools-D
(integer) 3
127.0.0.1:6379> SADD S3 tools-C tools-D
(integer) 2
127.0.0.1:6379> SDIFF S1 S3 # S1 与 S3 的差集
1) "tools-B"
2) "tools-A"
127.0.0.1:6379> SDIFF S1 S2 # S1 与 S2 的差集
1) "tools-A"
127.0.0.1:6379> SDIFF S1 S2 S3 # S1 S2 S3 共同的差集
1) "tools-A"
6.6 SDIFFTORE 差集并保存
|----|---------------------------------------------------|-----------|--------------------------|
| 命令 | SDIFFSTORE destination key1 key2 key3 ... | 时间复杂度 | O(N),其中 N 表示所有给定集合中的元素总数 |
| 描述 | Set Difference Store → 差集 + 存储 计算 key1减去后面所有集合的差集,然后把结果存到 destination这个新集合里。 |||
差集规则:只保留【第一个集合有】、【其他所有集合都没有】的元素
结果 = key1 - (key2 ∪ key3 ∪ key4 ...)
cs
127.0.0.1:6379> SADD set1 a b c d
(integer) 4
127.0.0.1:6379> SADD set2 b c
(integer) 2
127.0.0.1:6379> SADD set3 d
(integer) 1
127.0.0.1:6379> SDIFFSTORE result set1 set2 set3
(integer) 1
127.0.0.1:6379> SMEMBERS result
1) "a"
# set1 有:a b c d 减去 set2(b c) 再减去 set3(d) 最后剩下:a
与 SDIFF 区别
|----------------|--------------------|--------|
| 命令 | 作用 | 是否保存结果 |
| SDIFF | 计算差集,只返回不保存 | ❌ 不保存 |
| SDIFFSTORE | 计算差集,并保存到新 key | ✅ 保存 |
最经典业务场景(必看!)
场景 1:推荐 "可能认识的人"
my_friends:我的好友your_friends:你的好友- 我和你共同好友:
SINTER - 我有、你没有的好友(推荐给你) :
SDIFFSTORE
cs
# 我的关注
127.0.0.1:6379> SADD my:follow 张三 李四
2
# 朋友的关注
127.0.0.1:6379> SADD friend:follow 张三 李四 王五 赵六
4
# 差集: 好友关注 - 我关注 = 推荐我关注
127.0.0.1:6379> SDIFF friend:follow my:follow
赵六
王五
# 保存推荐结果
127.0.0.1:6379> SDIFFSTORE recommend:follow friend:follow my:follow
2
场景 2:未读消息
-
all_msg:所有消息 -
read_msg:已读消息 -
未读消息 = 全部 - 已读
SDIFFSTORE unread_msg all_msg read_msg
场景 3:黑名单过滤
-
all_users:所有用户 -
black_users:黑名单 -
可展示用户 = 全部 - 黑名单
SDIFFSTORE show_users all_users black_users
6.7 SINTER & SINTERSTORE 交集命令
|----|----------------------------|-----------|------------------------------------------------|
| 命令 | SINTER key key ... | 时间复杂度 | 最坏情况下的时间复杂度为 O(N*M),其中 N 表示最小集合的基数,M 则表示集合的数量 |
| 描述 | Set Intersect → 求多个集合的 交集,返回【所有集合里 都同时存在】的元素 |||
cs
127.0.0.1:6379> SADD set1 a b c d
(integer) 4
127.0.0.1:6379> SADD set2 b c d e
(integer) 4
127.0.0.1:6379> SADD set3 c d f g
(integer) 4
127.0.0.1:6379> SINTER set1 set2 set3 # 因为只有 c、d 是三个集合都有的。
1) "c"
2) "d"
|-----------------|-------------------|-------|
| 命令 | 作用 | 是否保存 |
| SINTER | 求交集,直接返回结果 | ❌ 不保存 |
| SINTERSTORE | 求交集,并保存到新 key | ✅ 保存 |
# 只看结果
127.0.0.1:6379> SINTER set1 set2 set3
1) "c"
2) "d"
# 把结果保存到新集合 resultX
127.0.0.1:6379> SINTERSTORE resultX set1 set2
(integer) 3
127.0.0.1:6379> SMEMBERS resultX
1) "b"
2) "c"
3) "d"
场景案例-共同好友(最经典)
cs
# 用户A关注的人
127.0.0.1:6379> SADD user:follow:A 张三 李四 王五 赵六
4
# 用户B关注的人
127.0.0.1:6379> SADD user:follow:B 李四 王五 钱七 孙八
4
# A和B共同关注的人(交集)
127.0.0.1:6379> SINTER user:follow:A user:follow:B
李四
王五
# 把结果保存到新key(方便后续使用)
127.0.0.1:6379> SINTERSTORE common:follow user:follow:A user:follow:B
2
结果:李四、王五
6.8 SUNION & SUNIONSTORE 并集
|----|----------------------------|-----------|---------------------------|
| 命令 | SUNION key key ... | 时间复杂度 | O(N),其中 N 表示所有给定集合中的元素总数。 |
| 描述 | Set Union → 求多个集合的 并集。 把所有集合的元素合并在一起,自动去重,返回最终结果 |||
|----|---------------------------------------------|-----------|---------------------------|
| 命令 | SUNIONSTORE destination key key ... | 时间复杂度 | O(N),其中 N 表示所有给定集合中的元素总数。 |
| 描述 | Set Union → 求多个集合的 并集。 把所有集合的元素合并在一起,自动去重,返回最终结果放到 destination |||
|-----------------|------------------|--------|
| 命令 | 作用 | 是否保存结果 |
| SUNION | 求并集,直接返回 | ❌ 不保存 |
| SUNIONSTORE | 求并集,保存到新 key | ✅ 保存 |
并集案例如下
cs
127.0.0.1:6379> SADD set a b c
(integer) 3
127.0.0.1:6379> SADD set2 c d e
(integer) 3
127.0.0.1:6379> SADD set3 e f g
(integer) 3
# 求并集
127.0.0.1:6379> SUNION set1 set2 set3
1) "f"
2) "d"
3) "e"
4) "g"
5) "c"
127.0.0.1:6379>
所有元素合在一起,重复的 c、e 只保留一个
cs
# 求并集,保存到新 resultX
127.0.0.1:6379> SUNIONSTORE resultX set1 set2 set3
(integer) 5
127.0.0.1:6379> SMEMBERS resultX
1) "c"
2) "d"
3) "e"
4) "f"
5) "g"
当前所有的命令都是属于 Set 数据类型的操作命令,所以新的集合 resultX 等等也都是属于 Set 数据类型,因此该数据类型是:无序,不重复的数据集合。
场景 1:合并所有好友 / 粉丝列表
- 用户 A 的好友
- 用户 B 的好友全部好友汇总 = 并集
redis
SUNION user:A:friends user:B:friends
场景 2:多标签文章查询(满足任意一个)
- 标签 1:Redis
- 标签 2:MySQL
- 标签 3:Java
只要带其中任意一个标签的文章 = 并集
场景 3:全网热搜 / 内容聚合
把多个榜单的内容合并,自动去重,展示总榜单。
三个集合命令 终极对比(必背!)
|------------|--------|------------|
| 命令 | 含义 | 口诀 |
| SINTER | 交集 | 大家都有的 |
| SDIFF | 差集 | 我有你没有的 |
| SUNION | 并集 | 全部合在一起 |
6.9 SMOVE 跨集合移动元素
|----|-------------------------------------|-----------|------|
| 命令 | SMOVE source destination member | 时间复杂度 | O(1) |
| 描述 | SMOVE 源集合 目标集合 元素 一句话:原子操作,把元素从源集合删掉,添加到目标集合 |||
示例
cpp
127.0.0.1:6379> SADD src a b c
(integer) 3
127.0.0.1:6379> SADD dest c d
(integer) 2
# # 把b从src移到dest
127.0.0.1:6379> SMOVE src dest b
(integer) 1
127.0.0.1:6379> SMEMBERS dest
1) "c"
2) "d"
3) "b"
127.0.0.1:6379> SMEMBERS src
1) "a"
2) "c"
业务场景
- 用户:待审核→已通过分组迁移;
- 商品:临时库存→正式上架分组挪动;
- 黑名单:临时拉黑→永久拉黑迁移。
6.10 SCARD
|----|---------------|-----------|------|
| 命令 | SCARD key | 时间复杂度 | O(1) |
| 描述 | 全称:Set Cardinality(集合基数) 获取一个集合里有多少个元素(统计集合大小) |||
示例
cpp
127.0.0.1:6379> SADD myset a b c d # 添加4个元素
(integer) 4
127.0.0.1:6379> SCARD myset # 返回:4
(integer) 4
- 如果集合 不存在 ,返回 0
- 时间复杂度 O(1),超级快(Redis 内部直接存了长度)
业务场景
- 统计点赞数
- 统计在线人数
- 统计标签数量
- 统计好友数量
6.11 SISMEMBER 命令
|----|--------------------------|-----------|------|
| 命令 | SISMEMBER key member | 时间复杂度 | O(1) |
| 描述 | 全称:Set Is Member(是不是集合成员)判断一个元素 是否存在于集合中 |||
cpp
127.0.0.1:6379> SADD myset a b c
(integer) 3
127.0.0.1:6379> SISMEMBER myset a
(integer) 1 # 1 表示存在
127.0.0.1:6379> SISMEMBER myset z
(integer) 0 # 0 表示不存在