结合前两期 Redis(一) Redis简介(Redis(一) Redis简介-CSDN博客) Redis(二) 可编程性(Redis(二) 可编程性-CSDN博客)
目录
事务在 Redis 中的运作方式
用法
事务中的错误
回滚
放弃命令队列
使用检查和设置的乐观锁定
WATCH解释
使用 WATCH 实现 ZPOP
Redis 发布/订阅
推送消息的格式
有线协议示例
模式匹配订阅
与模式和频道订阅匹配的消息
模式匹配的订阅计数的含义
事务在 Redis 中的运作方式
Redis 事务允许执行一组命令 在一个步骤中,它们以 MULTI、EXEC、DISCARD 和 WATCH 命令为中心。 Redis 事务提供两个重要保证:
-
事务中的所有命令都经过序列化和执行 顺序。其他客户端发送的请求永远不会 在 Redis 事务执行过程中提供服务。 这保证了命令作为单个命令执行 隔离操作。
-
EXEC 命令 触发事务中所有命令的执行,因此 如果客户端在上下文中失去与服务器的连接 调用 EXEC 命令前的事务,无任何操作 ,相反,如果调用 EXEC 命令,则所有 执行操作。使用仅追加文件时,Redis 确保 使用单个 write(2) 系统调用将事务写入磁盘。 但是,如果 Redis 服务器崩溃或被系统管理员杀死 以某种困难的方式,可能只有部分操作 已注册。Redis 将在重新启动时检测到此情况,并退出并显示错误。 使用该工具可以修复 仅附加将删除部分事务的文件,以便 服务器可以重新启动。redis-check-aof
从版本 2.2 开始,Redis 允许对 在两个以上,以一种非常类似于 检查和设置 (CAS) 操作。
用法
使用 MULTI 命令输入 Redis 事务。命令 总是回复 .此时,用户可以发出多个 命令。Redis 不会执行这些命令,而是会排队 他们。调用 EXEC 后,将执行所有命令。OK
改为调用 DISCARD 将刷新事务队列并退出 交易。
以下示例以原子方式递增键。foobar
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
从上面的会话中可以清楚地看出,EXEC 返回一个 回复数组,其中每个元素都是单个命令的回复 在事务中,命令的发出顺序相同。
当 Redis 连接位于 MULTI 请求的上下文中时, 所有命令都将回复字符串(作为状态回复发送 从Redis协议的角度来看)。排队的命令是 只需在调用 EXEC 时安排执行。QUEUED
事务中的错误
在事务过程中,可能会遇到两种命令错误:
- 命令可能无法排队,因此在调用 EXEC 之前可能会出现错误。 例如,命令可能在语法上是错误的(参数数量错误, 错误的命令名称,...),或者可能存在一些关键情况,例如 内存条件(如果使用指令将服务器配置为具有内存限制)。maxmemory
- 调用 EXEC 后,命令可能会失败,例如,由于我们执行了 对具有错误值的键的操作(例如针对字符串值调用列表操作)。
从 Redis 2.6.5 开始,服务器会在命令累积过程中检测到错误。 然后,它将拒绝执行事务,并在 EXEC 期间返回错误,从而丢弃事务。
在 Redis 2.6.5 之前,客户端需要通过检查来检测 EXEC 之前发生的错误 queued 命令的返回值:如果命令回复 QUEUED,则为 正确排队,否则 Redis 返回错误。 如果在对命令进行排队时出现错误,则大多数客户端 将中止并放弃事务。否则,如果客户选择继续交易 EXEC 命令将执行所有成功排队的命令,而不考虑以前的错误。
EXEC 之后发生的错误不会以特殊方式处理: 即使某些命令在事务期间失败,也会执行所有其他命令。
这在协议级别上更为明确。在以下示例中,一个 即使语法正确,命令在执行时也会失败:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MULTI
+OK
SET a abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-WRONGTYPE Operation against a key holding the wrong kind of value
EXEC 返回了两个元素的批量字符串回复,其中一个是代码和 另一个是错误回复。由客户端库来查找 向用户提供错误的明智方式。OK
需要注意的是,即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止 命令的处理。
另一个示例,再次使用 wire 协议和 ,显示了如何 语法错误会尽快报告:telnet
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command
这一次,由于语法错误,错误的 INCR 命令未排队 完全。
回滚
Redis 不支持事务回滚,因为支持回滚 将对 Redis 的简单性和性能产生重大影响。
放弃命令队列
DISCARD 可用于中止事务。在这种情况下,不可以 执行命令并将连接状态恢复到 正常。
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"
使用检查和设置的乐观锁定
WATCH 用于向 Redis 提供检查和设置 (CAS) 行为 交易。
监视 WATCHed 密钥,以检测针对它们的更改。如果 在 EXEC 命令之前,至少修改了一个监视密钥, 整个事务中止,EXEC 返回 Null 回复以通知 事务失败。
例如,假设我们需要以原子方式递增值 键的 1(假设 Redis 没有 INCR)。
第一次尝试可能如下:
val = GET mykey
val = val + 1
SET mykey $val
只有当我们有一个客户端执行 在给定时间内操作。如果多个客户端尝试递增密钥 大约在同一时间,将有一个竞争条件。例如 客户端 A 和 B 将读取旧值,例如 10。该值将 由两个客户端递增到 11,最后 SET 作为值 的键。因此,最终值将是 11 而不是 12。
多亏了 WATCH,我们才能很好地对问题进行建模:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代码,如果存在竞争条件和另一个客户端 修改 in 我们调用 WATCH 和 我们对 EXEC 的调用,事务将失败。val
我们只需要重复操作,希望这次我们不会得到 新种族。这种锁定形式称为乐观锁定。 在许多用例中,多个客户端将访问不同的密钥, 因此,不太可能发生碰撞 - 通常无需重复操作。
WATCH解释
那么WATCH到底是什么呢?这是一个命令,将 使 EXEC 有条件:我们要求 Redis 执行 仅当 WATCHed 密钥均未修改时,才进行交易。这包括 客户端所做的修改,如写入命令,以及 Redis 本身, 比如过期或驱逐。如果在监视和接收 EXEC 之间修改了密钥,则整个事务将被中止 相反。
注意:
- 在 6.0.9 之前的 Redis 版本中,过期的密钥不会导致事务 要中止。
- 事务中的命令不会触发 WATCH 条件,因为它们 仅在发送 EXEC 之前排队。
WATCH可以多次调用。简单地说,所有的 WATCH 调用都会 从通话开始,可以监视变化的效果,直到 调用 EXEC 的那一刻。您还可以将任意数量的密钥发送到 单次观看通话。
调用 EXEC 时,无论是否 事务是否中止。此外,当客户端连接是 关闭,一切都变得 UNWATCHed。
也可以使用 UNWATCH 命令(不带参数) 为了刷新所有监视的键。有时这很有用,因为我们 乐观地锁定几个键,因为可能需要执行 事务来更改这些键,但在读取当前内容后 我们不想继续的键。发生这种情况时,我们只需调用 UNWATCH,以便该连接已经可以自由地用于新的 交易。
使用 WATCH 实现 ZPOP
一个很好的例子来说明如何使用 WATCH 来创建新的 否则 Redis 不支持的原子操作是实现 ZPOP (仅添加了 ZPOPMIN、ZPOPMAX 及其阻塞变体 在 5.0 版中),这是一个弹出元素的命令,具有较低的 以原子方式从排序集得分。这是最简单的 实现:
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
如果 EXEC 失败(即返回 Null 回复),我们只需重复该操作。
Redis 发布/订阅
SUBSCRIBE、UNSUBSCRIBE 和 PUBLISH 实现了发布/订阅消息传递范式,其中(引用维基百科)发送者(发布者)没有被编程为将其消息发送给特定的接收者(订阅者)。 相反,已发布的消息被定性为频道,而不知道可能有哪些(如果有的话)订阅者。 订阅者对一个或多个频道表示兴趣,并且只收到感兴趣的消息,而不知道有哪些(如果有)发布者。 发布者和订阅者的这种解耦允许更大的可伸缩性和更动态的网络拓扑。
例如,要订阅频道“channel11”和“ch:00”,客户端会发出 SUBSCRIBE,提供频道名称:
SUBSCRIBE channel11 ch:00
其他客户端发送到这些通道的消息将由 Redis 推送到所有订阅的客户端。 订阅者按照消息的发布顺序接收消息。
订阅一个或多个频道的客户端不应发出命令,尽管它可以对其他频道进行 SUBSCRIBE 和取消订阅。 对订阅和取消订阅操作的回复以消息的形式发送,以便客户端可以只读取连贯的消息流,其中第一个元素指示消息类型。 在订阅的 RESP2 客户端的上下文中允许的命令包括:
- PING
- PSUBSCRIBE
- PUNSUBSCRIBE
- QUIT
- RESET
- SSUBSCRIBE
- SUBSCRIBE
- SUNSUBSCRIBE
- UNSUBSCRIBE
但是,如果使用 RESP3(请参阅 HELLO),则客户端可以在订阅状态下发出任何命令。
请注意,在订阅模式下使用 时,不能使用 UNSUBSCRIBE 和 PUNSUBSCRIBE 等命令,因为不接受任何命令,只能退出 WITH 模式。redis-cliredis-cliCtrl-C
推送消息的格式
消息是具有三个元素的数组回复。
第一个元素是消息的类型:
subscribe:表示我们成功订阅了回复中作为第二个元素给出的频道。 第三个参数表示我们当前订阅的频道数量。
unsubscribe:表示我们已成功取消订阅回复中作为第二个元素给出的频道。 第三个参数表示我们当前订阅的频道数量。 当最后一个参数为零时,我们不再订阅任何通道,客户端可以发出任何类型的 Redis 命令,因为我们处于 Pub/Sub 状态之外。
message:它是由于另一个客户端发出的 PUBLISH 命令而接收的消息。 第二个元素是原始通道的名称,第三个参数是实际的消息负载。
有线协议示例
SUBSCRIBE first second
*3
$9
subscribe
$5
first
:1
*3
$9
subscribe
$6
second
:2
此时,我们从另一个客户端对名为 :second
> PUBLISH second Hello
这是第一个客户端收到的内容:
*3
$7
message
$6
second
$5
Hello
现在,客户端使用 UNSUBSCRIBE 命令从所有通道中取消订阅,而无需其他参数:
UNSUBSCRIBE
*3
$11
unsubscribe
$6
second
:1
*3
$11
unsubscribe
$5
first
:0
模式匹配订阅
Redis Pub/Sub 实现支持模式匹配。 客户端可以订阅 glob 样式模式,以接收发送到与给定模式匹配的通道名称的所有消息。
例如:
PSUBSCRIBE news.*
将接收发送到频道的所有消息,等等。 所有 glob 样式模式都有效,因此支持多个通配符。news.art.figurative
news.music.jazz
PUNSUBSCRIBE news.*
然后,将取消订阅该模式的客户端。 此调用不会影响其他订阅。
由于模式匹配而收到的消息以不同的格式发送:
- 消息的类型是:它是从另一个客户端发出的 PUBLISH 命令接收的消息,与模式匹配订阅匹配。 第二个元素是匹配的原始模式,第三个元素是原始通道的名称,最后一个元素是实际的消息负载。
pmessage
与 SUBSCRIBE 和 UNSUBSUBSCRIBE 类似, 和 PUNSUBSCRIBE 命令由发送类型为消息的系统确认,并使用与 and 消息格式相同的格式。psubscribe
punsubscribe
subscribe
unsubscribe
与模式和频道订阅匹配的消息
如果客户端订阅了与已发布消息匹配的多个模式,或者订阅了与该消息匹配的模式和通道,则客户端可能会多次收到一条消息。 以下示例显示了这一点:
SUBSCRIBE foo
PSUBSCRIBE f*
在上面的示例中,如果将消息发送到 通道,客户端将收到两条消息:一条是 type 的消息,另一条是 type 的消息。foo
message
pmessage
模式匹配的订阅计数的含义
在 、 和 消息类型中,最后一个参数是仍处于活动状态的订阅计数。 此数字是客户端仍订阅的通道和模式的总数。 因此,只有当此计数因取消订阅所有通道和模式而降至零时,客户端才会退出 Pub/Sub 状态。subscribe
unsubscribe
psubscribe
punsubscribe
分片发布/订阅
从 Redis 7.0 开始,引入了分片 Pub/Sub,其中分片通道通过用于为插槽分配密钥的相同算法分配给插槽。 必须将分片消息发送到拥有分片通道散列到的插槽的节点。 群集确保将发布的分片消息转发到分片中的所有节点,以便客户端可以通过连接到负责槽位的主节点或其任何副本来订阅分片通道。SSUBSCRIBE、SUNSUBSCRIBE 和 SPUBLISH 用于实现分片的 Pub/Sub。
分片发布/订阅有助于在集群模式下扩展发布/订阅的使用。 它将消息的传播限制在集群的分片内。 因此,与全局发布/订阅相比,通过集群总线的数据量是有限的,在全局发布/订阅中,每条消息都传播到集群中的每个节点。 这允许用户通过添加更多分片来横向扩展 Pub/Sub 使用量。