Redis 数据结构

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 处的值的类型对应的字符串表示形式。可能返回的类型包括: stringlistsetzsethashstreamvectorset |||

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 位置的列表中的指定元素。 startstop 是基于 0 的索引 |||

LRANGERedis 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"

业务场景

  1. 用户:待审核→已通过分组迁移;
  2. 商品:临时库存→正式上架分组挪动;
  3. 黑名单:临时拉黑→永久拉黑迁移。

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 表示不存在