Featured image of post Redis 进阶

Redis 进阶

Redis 事务、过期时间、排序、消息通知

Redis 进阶

1. Redis 事务

Redis 中的事务(transaction)是一个单独的操作序列,可以一次执行多个命令,但是这些命令要么全部执行,要么全部不执行。Redis 事务的实现是通过 MULTI、EXEC、DISCARD 和 WATCH 这四个命令来完成的。

1.1 MULTI 使用

MULTI 命令用于开启一个事务,它总是返回 OK。 MULTI 执行之后,客户端可以继续输入多条命令,这些命令不会立即执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。

1
2
3
4
MULTI
SET key1 value1
SET key2 value2
EXEC

1.2 EXEC 使用

EXEC 命令用于执行所有事务块内的命令。如果事务块内的命令全部执行成功,返回事务块内所有命令的返回值,否则返回一个错误。 Redis 保证一个事务中的所有命令都会被执行,不会出现部分执 行的情况。如果在发送EXEC命令之前发生错误,那么事务将被取消,所有命令都不会执行。

1.3 DISCARD 使用

DISCARD 命令用于取消事务,它清空事务队列并且恢复到正常模式。

1.4 WATCH 使用

WATCH 命令用于在事务执行之前监视任意数量的键。如果在事务执行之前这些键被其他命令所改动,那么事务将被打断。

注意: WATCH使用的是乐观锁,不是悲观锁。 那么,乐观锁和悲观锁有什么区别呢?

乐观锁和悲观锁是并发控制的两种策略。悲观锁认为数据会被其他事务修改,因此在对数据进行操作时会锁住数据,直到操作完成。乐观锁认为数据不会被其他事务修改,因此在对数据进行操作时不会锁住数据,只是在更新数据时判断数据是否被其他事务修改过。如果数据没有被修改过,则更新成功;如果数据被修改过,则更新失败。

示例:

1
2
3
4
5
6
7
set key 1
watch key
set key 2
multi
set key 3
exec
get key

输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
127.0.0.1:6379> get key
"2"
127.0.0.1:6379>  set key 1
OK
127.0.0.1:6379>   watch key
OK
127.0.0.1:6379>   set key 2
OK
127.0.0.1:6379>   multi
OK
127.0.0.1:6379>   set key 3
QUEUED
127.0.0.1:6379>   exec
(nil)
127.0.0.1:6379> get key
"2"

从上面示例我们可以看出,当某个键被 WATCH 监视后,如果在事务执行之前这个键被其他命令所改动,那么事务将被打断。无法执行事务中的命令,返回nil

2. Redis 过期时间

Redis 中可以为 key 设置过期时间,当 key 过期时,会自动删除。过期时间可以通过 EXPIRE、PEXPIRE、EXPIREAT、PEXPIREAT 四个命令来设置。

2.1 EXPIRE 使用

EXPIRE 命令用于设置 key 的过期时间,单位为秒。当 key 过期时,会自动删除。

EXPIRE 命令的使用方法为 EXPIRE key seconds,其中 key 为键名,seconds 为过期时间。

例如要让session:342r 60秒后过期,可以使用如下命令:

1
2
SET session:342r 11
EXPIRE session:342r 60

执行上述代码后,我们接着执行GET session:342r命令,会发现返回的是11,说明 key 还存在。但是等待 60 秒后再次执行GET session:342r命令,会发现返回的是(nil),说明 key 已经过期被删除了。

1
2
3
4
5
6
7
8
127.0.0.1:6379> SET session:342r 11
OK
127.0.0.1:6379> EXPIRE session:342r 60
(integer) 1
127.0.0.1:6379> GET session:342r
"11"
127.0.0.1:6379> GET session:342r
(nil)

想要知道 key 的剩余过期时间,可以使用 TTL 命令,TTL 命令的使用方法为 TTL key,其中 key 为键名。

1
TTL session:342r

将会返回 key 的剩余过期时间,单位为秒。如果 key 没有设置过期时间,或者 key 已经过期,TTL 命令会返回 -2。如果 key 存在但没有设置过期时间,TTL 命令会返回 -1。

在 Redis 2.6 版本中,无论键不存在还是没有过期时间,都会返回 -2。在 Redis 2.8 版本后,无论键不存在还是没有过期时间,都会返回 -1。

2.2 PERSIST 使用

PERSIST 命令用于移除 key 的过期时间,使 key 永不过期。如果过期时间移除成功,返回 1;如果 key 不存在或者 key 没有设置过期时间,返回 0。

PERSIST 命令的使用方法为 PERSIST key,其中 key 为键名。

1
PERSIST session:342r

2.3 PEXPIRE 使用

PEXPIRE 命令用于设置 key 的过期时间,单位为毫秒。当 key 过期时,会自动删除。

PEXPIRE 命令的使用方法为 PEXPIRE key milliseconds,其中 key 为键名,milliseconds 为过期时间。

例如要让session:342r 60秒后过期,可以使用如下命令:

1
2
SET session:342r 11
PEXPIRE session:342r 60000

2.4 EXPIREAT 使用

EXPIREAT 命令用于设置 key 的过期时间,以 UNIX 时间戳(unix timestamp)格式设置过期时间。当 key 过期时,会自动删除。

EXPIREAT 命令的使用方法为 EXPIREAT key timestamp,其中 key 为键名,timestamp 为过期时间。

例如要让session:342r 60秒后过期,可以使用如下命令:

1
2
SET session:342r 11
EXPIREAT session:342r 1646880000

2.5 PEXPIREAT 使用

PEXPIREAT 命令用于设置 key 的过期时间,以 UNIX 时间戳(unix timestamp)格式设置过期时间,单位为毫秒。当 key 过期时,会自动删除。

PEXPIREAT 命令的使用方法为 PEXPIREAT key timestamp,其中 key 为键名,timestamp 为过期时间。

例如要让session:342r 60秒后过期,可以使用如下命令:

1
2
SET session:342r 11
PEXPIREAT session:342r 1646880000000

2.6 设置过期时间的注意事项

  1. 如果 key 已经设置了过期时间,再次设置过期时间会覆盖之前的过期时间。

  2. 如果 key 已经过期,再次设置过期时间不会生效,key 会立即被删除。

  3. 如果 key 已经设置了过期时间,再次设置过期时间时,过期时间的单位不同,会覆盖之前的过期时间。

2.7 过期时间的应用场景

  1. 缓存数据:可以为缓存数据设置过期时间,当数据过期时,自动删除。

    • 比如要设置一个缓存数据为 5 分钟,可以使用如下命令:
    1
    2
    
    SET cache:342r 11
    EXPIRE cache:342r 300
    
    • 代码解释:
      • SET cache:342r 11:设置 cache:342r 键的值为 11。
      • EXPIRE cache:342r 300:设置 cache:342r 键的过期时间为 300 秒。
  2. 会话管理:可以为会话设置过期时间,当会话过期时,自动删除。

    • 比如要设置一个会话为 30 分钟,可以使用如下命令:
    1
    2
    
    SET session:342r 11
    EXPIRE session:342r 1800
    
    • 代码解释:
      • SET session:342r 11:设置 session:342r 键的值为 11。
      • EXPIRE session:342r 1800:设置 session:342r 键的过期时间为 1800 秒。
  3. 验证码:可以为验证码设置过期时间,当验证码过期时,自动删除。

    • 比如要设置一个验证码为 5 分钟,可以使用如下命令:
    1
    2
    
    SET code:342r 123456
    EXPIRE code:342r 300
    
    • 代码解释:
      • SET code:342r 123456:设置 code:342r 键的值为 123456。
      • EXPIRE code:342r 300:设置 code:342r 键的过期时间为 300 秒。
  4. 限流:可以为限流的 key 设置过期时间,当限流的 key 过期时,自动删除。

    • 比如要限制用户每分钟访问100个网页,思路是对每个用户使用一个名为rate.limiting:ip的字符串类型键,每次访问网页时,对这个键进行自增操作,如果自增后的值大于100,就拒绝访问。为了防止这个键一直存在,可以设置过期时间,比如设置为1分钟。
    • 代码示例:
    1
    2
    
    INCR rate.limiting:ip
    EXPIRE rate.limiting:ip 60
    
    • 代码解释:
      • INCR rate.limiting:ip:对 rate.limiting:ip 键进行自增操作。
      • EXPIRE rate.limiting:ip 60:设置 rate.limiting:ip 键的过期时间为 60 秒。

2.8 实现缓存

为了提高网站的负载能力,需要将一些访问频率较高的数据缓存到 Redis 中,并且希望让这些缓存过一段时间自动过期。比如,教务网站要将所有学生各个科目的成绩汇总排名,并在首页上显示排名前十的学生,这个数据是实时变化的,并且非常消耗计算资源,所以不能每次都去数据库查询,而是将这个数据缓存到 Redis 中,并设置过期时间为 2 小时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
SET ranking:student1 100
SET ranking:student2 99
SET ranking:student3 98
SET ranking:student4 97
SET ranking:student5 96
SET ranking:student6 95
SET ranking:student7 94
SET ranking:student8 93
SET ranking:student9 92
SET ranking:student10 91
EXPIRE ranking:student1 7200
EXPIRE ranking:student2 7200
EXPIRE ranking:student3 7200
EXPIRE ranking:student4 7200
EXPIRE ranking:student5 7200
EXPIRE ranking:student6 7200
EXPIRE ranking:student7 7200
EXPIRE ranking:student8 7200
EXPIRE ranking:student9 7200
EXPIRE ranking:student10 7200

如果服务器内存有限,如果大量地设置缓存且过期时间较长,可能会导致服务器内存不足。为了解决这个问题,可以使用 Redis 的淘汰策略。

我们可以通过设置配置文件来解决这个问题,修改配置文件中的maxmemory参数,设置 Redis 的最大内存限制。当 Redis 的内存使用达到最大内存限制时,Redis 会根据配置文件中的maxmemory-policy参数来决定淘汰策略。

maxmemory-policy 参数有以下几种取值:

  • volatile-lru:在设置了过期时间的 key 中,优先淘汰最近最少使用的 key。
  • volatile-ttl:在设置了过期时间的 key 中,优先淘汰剩余时间最少的 key。
  • volatile-random:在设置了过期时间的 key 中,随机淘汰 key。
  • allkeys-lru:在所有 key 中,优先淘汰最近最少使用的 key。
  • allkeys-random:在所有 key 中,随机淘汰 key。
  • noeviction:不淘汰任何 key,只是返回错误。

其中,LRULeast Recently Used的缩写,表示最近最少使用,TTLTime To Live的缩写,表示剩余时间。

3. Redis 排序

有序集合不支持交、并运算,Redis 还未加入相关命令。这是因为 Redis 认为开发者在做完交、并操作后不需要直接获得全部结果,而是希望将结果存入新的键中以便后续处理。所以没有序集合只有ZINTERSTOREZUNIONSTORE

3.1 SORT 命令

SORT命令可以对列表类型、集合类型、有序集合类型的值进行排序。 SORT命令的使用方法为SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]⚠️注意:SORT命令至对输出结果进行排序,并不对数值进行改变!!!

3.1.1 排列列表

SORT命令可以对列表类型的值进行排序。比如,对一个列表类型的值进行排序,可以使用如下命令:

1
2
3
lpush list 324 45 6 75 78 89
sort list # 默认升序
lrange list # 输出所有元素

输出结果为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
127.0.0.1:6379> lpush list 324 45 6 75 78 89
(integer) 6
127.0.0.1:6379> sort list
1) "6"
2) "45"
3) "75"
4) "78"
5) "89"
6) "324"
127.0.0.1:6379> lrange list 0 -1
1) "89"
2) "78"
3) "75"
4) "6"
5) "45"
6) "324"

3.1.2 排列集合

SORT命令可以对集合类型的值进行排序。比如,对一个集合类型的值进行排序,可以使用如下命令:

1
2
3
sadd set 324 45 6 75 78 89
sort set # 默认升序
smembers set # 输出所有元素

在小数据集的情况下,smembers 输出的看似是有序的,但实际上是无序的。可以通过 sort 命令进行排序。 Redis 集合(Set) 的底层数据结构 主要是哈希表(hashtable)或者整数集合(intset)(当元素全是整数且个数较少时)。 在 SMEMBERS 输出时,哈希表的遍历方式可能会让元素按某种规则排列,但这不是保证的排序,只是某些情况下看起来有序。

3.1.3 排列有序集合

SORT命令可以对有序集合类型的值进行排序。比如,对一个有序集合类型的值进行排序,可以使用如下命令:

1
2
3
zadd zset 324 324 45 45 6 6 75 75 78 78 89 89
sort zset # 默认升序
zrange zset 0 -1 # 输出所有元素

注意;SORT命令对有序集合类型的值进行排序时,会将分数相同的元素按照成员的字典序进行排序。前面是分数,后面是成员。SORT 排列的是成员,而不是分数。如果成员是数字,那么按照数字的大小进行排序;如果成员是字符串,那么必须加上ALPHA才能按照字符串的字典序进行排序。

3.1.4 LIMIT 参数

SORT命令的LIMIT参数用于限制输出结果的数量。LIMIT参数的使用方法为LIMIT offset count,其中offset为偏移量,count为数量。

偏移量(offset)表示从第几个元素开始,数量(count)表示输出多少个元素。

比如,对一个列表类型的值进行排序,并限制输出结果的数量,可以使用如下命令:

1
2
3
lpush list 324 45 6 75 78 89
sort list limit 0 3 # 默认升序
lrange list 0 -1 # 输出所有元素

输出结果为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
127.0.0.1:6379> lpush list 324 45 6 75 78 89
(integer) 12
127.0.0.1:6379> sort list limit 0 3
1) "6"
2) "6"
3) "45"
127.0.0.1:6379> lrange list 0 -1
 1) "89"
 2) "78"
 3) "75"
 4) "6"
 5) "45"
 6) "324"
 7) "89"
 8) "78"
 9) "75"
10) "6"
11) "45"
12) "324"

3.1.5 DESC 参数

SORT命令的DESC参数用于指定排序方式为降序。 例如:

1
2
lpush list 324 45 6 75 78 89
sort list desc

3.1.6 ALPHA 参数

SORT命令的ALPHA参数用于指定排序方式为字典序。

3.1.7 BY 参数

SORT命令的BY参数用于指定排序的依据。BY参数的使用方法为BY pattern,其中pattern为模式。 例如,我们先插入一些数据:

1
2
3
4
5
lpush sortlist 2 1 3
set item:1 10
set item:2 5
set item:3 30
sort sortlist by item:* desc # 按照 item:* 的值降序排序

这种情况下,SORT命令会先根据item:*的值对sortlist进行排序,然后再输出排序后的结果。

适用于需要根据特定属性对于集合进行排序的场景。

还可以是哈希表的键值对,例如:

1
2
3
4
5
lpush sortlist 2 1 3
hset item:1 score 10
hset item:2 score 5
hset item:3 score 30
sort sortlist by item:*->score desc # 按照 item:*->score 的值降序排序

3.1.8 GET 参数

GET 参数不影响排序,它的作用是使 POST 命令的返回结果不再是元素自身的值,而是GET 参数里面指定的键值。 例如:

1
2
3
4
5
lpush sortlist 2 1 3
set item:1 10
set item:2 5
set item:3 30
sort sortlist by item:* desc get item:* # 按照 item:* 的值降序排序,并且返回 item:* 的值

3.1.9 STORE 参数

SORT命令的STORE参数用于将排序后的结果保存到一个新的键中。STORE参数的使用方法为STORE destination,其中destination存储的键。

例如:

1
2
3
4
5
lpush sortlist 2 1 3
set item:1 10
set item:2 5
set item:3 30
sort sortlist by item:* desc store newlist # 按照 item:* 的值降序排序,并且将结果保存到 newlist 键中

4 Redis 消息通知

Redis 提供了 发布/订阅(Pub/Sub)、Key 事件通知、Stream(消息队列)三种消息通知机制。

4.1 发布/订阅

发布/订阅(Publish/Subscribe,简称 Pub/Sub) 是 Redis 提供的一种消息订阅机制,允许客户端订阅一个或多个频道(Channel),然后发布者向这些频道发送这些消息,所有订阅者都会收到相应的消息。

4.1.1 订阅频道(Subscribe)

SUBSCRIBE命令用于订阅一个或多个频道。SUBSCRIBE命令的使用方法为SUBSCRIBE channel [channel ...],其中channel为频道名。

示例:

1
subscribe channel

输出结果:

1
2
3
4
5
127.0.0.1:6379> subscribe channel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1

4.1.2 发布消息(Publish)

PUBLISH命令用于向指定频道发送消息。PUBLISH命令的使用方法为PUBLISH channel message,其中channel为频道名,message为消息内容。

在 4.1.1 例子上的基础上,新建一个控制台,执行如下命令:

1
publish channel "hello"

原来的控制台会输出:

1
2
3
4
5
6
7
8
127.0.0.1:6379> subscribe channel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1
1) "message"
2) "channel"
3) "hello"

多了一行"hello",说明发布成功,客户端已经收到消息。

4.1.3 取消订阅(UNSUBSCRIBE)

UNSUBSCRIBE命令用于取消订阅一个或多个频道。UNSUBSCRIBE命令的使用方法为UNSUBSCRIBE [channel [channel ...]],其中channel为频道名。

示例:

1
unsubscribe channel

4.1.4 订阅多个频道

SUBSCRIBE命令可以订阅多个频道。比如,订阅channel1channel2两个频道,可以使用如下命令:

1
subscribe channel1 channel2

4.1.5 取消订阅所有频道

UNSUBSCRIBE命令可以取消订阅所有频道。比如,取消订阅所有频道,可以使用如下命令:

1
unsubscribe

4.2 Key 事件通知

Redis Key 事件通知允许监听数据库键的变化事件,例如SETDELEXPIRE

4.2.1 配置通知

默认情况下,Redis 不会 开启Key 事件通知,可以通过以下命令启用:

1
config set notify-keyspace-events KEA

notify-keyspace-events参数的取值有以下几种:

  • K:键空间通知,事件以__keyspace@<db>__为前缀进行发布。(Key 事件通知 (keyspace events))

  • E:键事件通知,事件以__keyevent@<db>__为前缀进行发布。(keyevent)

  • A:参数通知,事件以__keyspace@<db>__为前缀进行发布。(ALL)

  • g:DEL、EXPIRE、RENAME、EXPIREAT、SORT操作的通知。

  • $:字符串命令通知。

4.2.2 订阅通知

PSUBSCRIBE命令用于订阅一个或多个模式。PSUBSCRIBE命令的使用方法为PSUBSCRIBE pattern [pattern ...],其中pattern为模式。

示例:

1
psubscribe __keyspace@0__:*

然后再另一个终端执行:

1
set mykey 1

订阅者会收到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
127.0.0.1:6379> psubscribe __keyspace@0__:*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyspace@0__:*"
3) (integer) 1
1) "pmessage"
2) "__keyspace@0__:*"
3) "__keyspace@0__:mykey"
4) "set"
1) "pmessage"
2) "__keyspace@0__:*"
3) "__keyspace@0__:mykey"
4) "del"

4.2.3 监听事件

事件:

  • set: 触发操作为SET key value
  • del: 触发操作为DEL key
  • expire: 触发操作为EXPIRE key seconds
  • rename: 触发操作为RENAME key newkey
  • expired: 键过期时触发。

注意:

Key 事件通知只能监键的变化,不能监值的变化。

4.3 Redis Stream(消息队列)

Redis Stream 是一个高效的消息队列,支持消息持久化、消费者组、消费确认等功能。适用于日志收集、事件流处理、消息队列等应用场景。

4.3.1 添加消息 (XADD)

基本语法:

1
XADD stream_name ID field1 value1 field2 value2 ...
  • stream_name:流的名称。
  • ID:消息的唯一标识,可以是*$
  • field1 value1 field2 value2 ...:消息的键值对。

举例:

1
XADD mystream * name Tom age 18

4.3.2 读取消息

4.3.2.1 读取所有消息 (XRANGE)

1
XRANGE mystream - +

会输出以下内容:

1
2
3
4
5
6
127.0.0.1:6379> XRANGE mystream - +
1) 1) "1741594483162-0"
   2) 1) "name"
      2) "Tom"
      3) "age"
      4) "18"

4.3.2.2 阻塞式读取新消息 (XREAD)

基本语法:

1
XREAD COUNT count BLOCK milliseconds STREAMS stream_name ID
  • count:读取消息的数量。
  • milliseconds:阻塞时间,单位为毫秒。
  • stream_name:流的名称。
  • ID:消息的唯一标识。(当$时,表示从最新的消息开始读取)

举例:

1
XREAD COUNT 1 STREAMS mystream 0 # 非阻塞式读取一条消息

还可以当有新的消息时,自动读取新消息:

1
XREAD BLOCK 5000 STREAMS mystream $ # 阻塞式读取新消息

此时切换到另一个终端,执行:

1
XADD mystream * name Tom age 18

第一个控制台会输出以下内容:

1
2
3
4
5
6
7
127.0.0.1:6379> XREAD COUNT 1 STREAMS mystream 0
1) 1) "mystream"
   2) 1) 1) "1741594483162-0"
         2) 1) "name"
            2) "Tom"
            3) "age"
            4) "18"

4.3.3 消费者组

4.3.4 创建消费者组

基本语法:

1
XGROUP CREATE stream_name group_name ID
  • stream_name:流的名称。
  • group_name:消费者组的名称。
  • ID:消息的唯一标识。

例如:

1
XGROUP CREATE mystream mygroup 0
4.3.5 消费者读取消息

基本语法:

1
XREADGROUP GROUP group_name consumer_name COUNT count BLOCK milliseconds STREAMS stream_name ID
  • group_name:消费者组的名称。
  • consumer_name:消费者的名称。
  • count:读取消息的数量。
  • milliseconds:阻塞时间,单位为毫秒。
  • stream_name:流的名称。
  • ID:消息的唯一标识。(*表示从最新的消息开始读取,>表示从上次读取的位置开始读取)

例如:

1
XREADGROUP GROUP mygroup myconsumer COUNT 1 STREAMS mystream > # 读取一条消息

4.3.6 消费者确认消息

基本语法:

1
XACK stream_name group_name ID
  • stream_name:流的名称。
  • group_name:消费者组的名称。
  • ID:消息的唯一标识。

例如:

1
XACK mystream mygroup 1741594483162-0

4.3.4 三者之间的比较

  • 发布/订阅(Pub/Sub):

    • ✅ 低延迟,适用于实时消息推送
    • ✅ 适合广播通知,如聊天系统、游戏推送
    • ✅ 订阅者断开连接后不会收到历史消息
    • ❌ 无法保证消息可靠性(可能丢失)
  • Key 事件通知:

    • ✅ 无法保证消息可靠性(可能丢失)
    • ✅ 可以配合 Redis 作为消息通知机制
    • ❌ 可能会增加 Redis 的 CPU 负担
    • ❌ 只能监听键的变化,不能监听数据值的变化
  • Stream(消息队列):

    • ✅ 支持消息持久化,不会丢失
    • ✅ 支持多个消费者,适用于高并发场景
    • ✅ 可以追踪消息处理状态
    • ❌ 比 Pub/Sub 稍微复杂,但更可靠
Licensed under CC BY-NC-SA 4.0