Redis 进阶
1. Redis 事务
Redis 中的事务(transaction)是一个单独的操作序列,可以一次执行多个命令,但是这些命令要么全部执行,要么全部不执行。Redis 事务的实现是通过 MULTI、EXEC、DISCARD 和 WATCH 这四个命令来完成的。
1.1 MULTI 使用
MULTI 命令用于开启一个事务,它总是返回 OK。 MULTI 执行之后,客户端可以继续输入多条命令,这些命令不会立即执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。
|
|
1.2 EXEC 使用
EXEC 命令用于执行所有事务块内的命令。如果事务块内的命令全部执行成功,返回事务块内所有命令的返回值,否则返回一个错误。 Redis 保证一个事务中的所有命令都会被执行,不会出现部分执 行的情况。如果在发送EXEC命令之前发生错误,那么事务将被取消,所有命令都不会执行。
1.3 DISCARD 使用
DISCARD 命令用于取消事务,它清空事务队列并且恢复到正常模式。
1.4 WATCH 使用
WATCH 命令用于在事务执行之前监视任意数量的键。如果在事务执行之前这些键被其他命令所改动,那么事务将被打断。
注意: WATCH使用的是乐观锁,不是悲观锁。 那么,乐观锁和悲观锁有什么区别呢?
乐观锁和悲观锁是并发控制的两种策略。悲观锁认为数据会被其他事务修改,因此在对数据进行操作时会锁住数据,直到操作完成。乐观锁认为数据不会被其他事务修改,因此在对数据进行操作时不会锁住数据,只是在更新数据时判断数据是否被其他事务修改过。如果数据没有被修改过,则更新成功;如果数据被修改过,则更新失败。
示例:
|
|
输出结果:
|
|
从上面示例我们可以看出,当某个键被 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秒后过期,可以使用如下命令:
|
|
执行上述代码后,我们接着执行GET session:342r
命令,会发现返回的是11
,说明 key 还存在。但是等待 60 秒后再次执行GET session:342r
命令,会发现返回的是(nil)
,说明 key 已经过期被删除了。
|
|
想要知道 key 的剩余过期时间,可以使用 TTL 命令,TTL 命令的使用方法为 TTL key,其中 key 为键名。
|
|
将会返回 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 为键名。
|
|
2.3 PEXPIRE 使用
PEXPIRE 命令用于设置 key 的过期时间,单位为毫秒。当 key 过期时,会自动删除。
PEXPIRE 命令的使用方法为 PEXPIRE key milliseconds,其中 key 为键名,milliseconds 为过期时间。
例如要让session:342r 60秒后过期,可以使用如下命令:
|
|
2.4 EXPIREAT 使用
EXPIREAT 命令用于设置 key 的过期时间,以 UNIX 时间戳(unix timestamp)格式设置过期时间。当 key 过期时,会自动删除。
EXPIREAT 命令的使用方法为 EXPIREAT key timestamp,其中 key 为键名,timestamp 为过期时间。
例如要让session:342r 60秒后过期,可以使用如下命令:
|
|
2.5 PEXPIREAT 使用
PEXPIREAT 命令用于设置 key 的过期时间,以 UNIX 时间戳(unix timestamp)格式设置过期时间,单位为毫秒。当 key 过期时,会自动删除。
PEXPIREAT 命令的使用方法为 PEXPIREAT key timestamp,其中 key 为键名,timestamp 为过期时间。
例如要让session:342r 60秒后过期,可以使用如下命令:
|
|
2.6 设置过期时间的注意事项
-
如果 key 已经设置了过期时间,再次设置过期时间会覆盖之前的过期时间。
-
如果 key 已经过期,再次设置过期时间不会生效,key 会立即被删除。
-
如果 key 已经设置了过期时间,再次设置过期时间时,过期时间的单位不同,会覆盖之前的过期时间。
2.7 过期时间的应用场景
-
缓存数据:可以为缓存数据设置过期时间,当数据过期时,自动删除。
- 比如要设置一个缓存数据为 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 秒。
-
会话管理:可以为会话设置过期时间,当会话过期时,自动删除。
- 比如要设置一个会话为 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 秒。
-
验证码:可以为验证码设置过期时间,当验证码过期时,自动删除。
- 比如要设置一个验证码为 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 秒。
-
限流:可以为限流的 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 秒。
- 比如要限制用户每分钟访问100个网页,思路是对每个用户使用一个名为
2.8 实现缓存
为了提高网站的负载能力,需要将一些访问频率较高的数据缓存到 Redis 中,并且希望让这些缓存过一段时间自动过期。比如,教务网站要将所有学生各个科目的成绩汇总排名,并在首页上显示排名前十的学生,这个数据是实时变化的,并且非常消耗计算资源,所以不能每次都去数据库查询,而是将这个数据缓存到 Redis 中,并设置过期时间为 2 小时。
|
|
如果服务器内存有限,如果大量地设置缓存且过期时间较长,可能会导致服务器内存不足。为了解决这个问题,可以使用 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,只是返回错误。
其中,LRU
是Least Recently Used
的缩写,表示最近最少使用,TTL
是Time To Live
的缩写,表示剩余时间。
3. Redis 排序
有序集合不支持交、并运算,Redis 还未加入相关命令。这是因为 Redis 认为开发者在做完交、并操作后不需要直接获得全部结果,而是希望将结果存入新的键中以便后续处理。所以没有序集合只有ZINTERSTORE
和ZUNIONSTORE
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
命令可以对列表类型的值进行排序。比如,对一个列表类型的值进行排序,可以使用如下命令:
|
|
输出结果为:
|
|
3.1.2 排列集合
SORT
命令可以对集合类型的值进行排序。比如,对一个集合类型的值进行排序,可以使用如下命令:
|
|
在小数据集的情况下,smembers 输出的看似是有序的,但实际上是无序的。可以通过 sort 命令进行排序。 Redis 集合(Set) 的底层数据结构 主要是哈希表(hashtable)或者整数集合(intset)(当元素全是整数且个数较少时)。 在 SMEMBERS 输出时,哈希表的遍历方式可能会让元素按某种规则排列,但这不是保证的排序,只是某些情况下看起来有序。
3.1.3 排列有序集合
SORT
命令可以对有序集合类型的值进行排序。比如,对一个有序集合类型的值进行排序,可以使用如下命令:
|
|
注意;SORT
命令对有序集合类型的值进行排序时,会将分数相同的元素按照成员的字典序进行排序。前面是分数,后面是成员。SORT 排列的是成员,而不是分数。如果成员是数字,那么按照数字的大小进行排序;如果成员是字符串,那么必须加上ALPHA
才能按照字符串的字典序进行排序。
3.1.4 LIMIT 参数
SORT
命令的LIMIT
参数用于限制输出结果的数量。LIMIT
参数的使用方法为LIMIT offset count
,其中offset
为偏移量,count
为数量。
偏移量(offset)表示从第几个元素开始,数量(count)表示输出多少个元素。
比如,对一个列表类型的值进行排序,并限制输出结果的数量,可以使用如下命令:
|
|
输出结果为:
|
|
3.1.5 DESC 参数
SORT
命令的DESC
参数用于指定排序方式为降序。
例如:
|
|
3.1.6 ALPHA 参数
SORT
命令的ALPHA
参数用于指定排序方式为字典序。
3.1.7 BY 参数
SORT
命令的BY
参数用于指定排序的依据。BY
参数的使用方法为BY pattern
,其中pattern
为模式。
例如,我们先插入一些数据:
|
|
这种情况下,SORT
命令会先根据item:*
的值对sortlist
进行排序,然后再输出排序后的结果。
适用于需要根据特定属性对于集合进行排序的场景。
还可以是哈希表的键值对,例如:
|
|
3.1.8 GET 参数
GET 参数不影响排序,它的作用是使 POST 命令的返回结果不再是元素自身的值,而是GET 参数里面指定的键值。 例如:
|
|
3.1.9 STORE 参数
SORT
命令的STORE
参数用于将排序后的结果保存到一个新的键中。STORE
参数的使用方法为STORE destination
,其中destination
存储的键。
例如:
|
|
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
为频道名。
示例:
|
|
输出结果:
|
|
4.1.2 发布消息(Publish)
PUBLISH
命令用于向指定频道发送消息。PUBLISH
命令的使用方法为PUBLISH channel message
,其中channel
为频道名,message
为消息内容。
在 4.1.1 例子上的基础上,新建一个控制台,执行如下命令:
|
|
原来的控制台会输出:
|
|
多了一行"hello",说明发布成功,客户端已经收到消息。
4.1.3 取消订阅(UNSUBSCRIBE)
UNSUBSCRIBE
命令用于取消订阅一个或多个频道。UNSUBSCRIBE
命令的使用方法为UNSUBSCRIBE [channel [channel ...]]
,其中channel
为频道名。
示例:
|
|
4.1.4 订阅多个频道
SUBSCRIBE
命令可以订阅多个频道。比如,订阅channel1
和channel2
两个频道,可以使用如下命令:
|
|
4.1.5 取消订阅所有频道
UNSUBSCRIBE
命令可以取消订阅所有频道。比如,取消订阅所有频道,可以使用如下命令:
|
|
4.2 Key 事件通知
Redis Key 事件通知允许监听数据库键的变化事件,例如SET
、DEL
、EXPIRE
。
4.2.1 配置通知
默认情况下,Redis 不会 开启Key 事件通知,可以通过以下命令启用:
|
|
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
为模式。
示例:
|
|
然后再另一个终端执行:
|
|
订阅者会收到:
|
|
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)
基本语法:
|
|
stream_name
:流的名称。ID
:消息的唯一标识,可以是*
或$
。field1 value1 field2 value2 ...
:消息的键值对。
举例:
|
|
4.3.2 读取消息
4.3.2.1 读取所有消息 (XRANGE)
|
|
会输出以下内容:
|
|
4.3.2.2 阻塞式读取新消息 (XREAD)
基本语法:
|
|
count
:读取消息的数量。milliseconds
:阻塞时间,单位为毫秒。stream_name
:流的名称。ID
:消息的唯一标识。(当$
时,表示从最新的消息开始读取)
举例:
|
|
还可以当有新的消息时,自动读取新消息:
|
|
此时切换到另一个终端,执行:
|
|
第一个控制台会输出以下内容:
|
|
4.3.3 消费者组
4.3.4 创建消费者组
基本语法:
|
|
stream_name
:流的名称。group_name
:消费者组的名称。ID
:消息的唯一标识。
例如:
|
|
4.3.5 消费者读取消息
基本语法:
|
|
group_name
:消费者组的名称。consumer_name
:消费者的名称。count
:读取消息的数量。milliseconds
:阻塞时间,单位为毫秒。stream_name
:流的名称。ID
:消息的唯一标识。(*
表示从最新的消息开始读取,>
表示从上次读取的位置开始读取)
例如:
|
|
4.3.6 消费者确认消息
基本语法:
|
|
stream_name
:流的名称。group_name
:消费者组的名称。ID
:消息的唯一标识。
例如:
|
|
4.3.4 三者之间的比较
-
发布/订阅(Pub/Sub):
- ✅ 低延迟,适用于实时消息推送
- ✅ 适合广播通知,如聊天系统、游戏推送
- ✅ 订阅者断开连接后不会收到历史消息
- ❌ 无法保证消息可靠性(可能丢失)
-
Key 事件通知:
- ✅ 无法保证消息可靠性(可能丢失)
- ✅ 可以配合 Redis 作为消息通知机制
- ❌ 可能会增加 Redis 的 CPU 负担
- ❌ 只能监听键的变化,不能监听数据值的变化
-
Stream(消息队列):
- ✅ 支持消息持久化,不会丢失
- ✅ 支持多个消费者,适用于高并发场景
- ✅ 可以追踪消息处理状态
- ❌ 比 Pub/Sub 稍微复杂,但更可靠