Code前端首页关于Code前端联系我们

民工兄弟Redis教程(六):事务详解

terry 2年前 (2023-09-26) 阅读数 179 #后端开发

什么是Redis事务

Redis事务的核心是一组命令。事务支持同时执行多个命令,并且事务中的所有命令都会被序列化。交易执行过程中,排队的命令会按顺序执行,其他客户端发送的命令请求不会插入到交易执行命令序列中。

综上所述:redis 事务是对队列中一系列命令的一次性、顺序且独占的执行。

Redi 的事务相关命令及使用方法

MULTI、EXEC、DISCARD、WATCH 是Redi 的事务相关命令。

  • MULTI:一旦事务开始,redis会将连续的命令逐一放入队列中,然后使用EXEC命令原子地执行这一系列命令。
  • EXEC:执行事务中的所有操作命令。
  • DISCARD:中止事务并中止事务块中所有命令的执行。
  • SE:监控一个或多个按键。如果在提交事务之前该键(或多个键)被其他命令更改,则事务将被中止,并且事务中的任何命令都不会被执行。
  • UNWATCH:停止WATCH对所有按键的监控。

标准交易执行

分别为k1和k2赋值,在交易中修改k1和k2,执行交易后检查k1和k2的值是否发生变化。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"22"
127.0.0.1:6379>

更多Redis学习文章请参见:NoSQL数据库系列-Redis。该系列持续更新。

交易取消

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 33
QUEUED
127.0.0.1:6379> set k2 34
QUEUED
127.0.0.1:6379> DISCARD
OK

交易错误处理

  • 语法错误(编译器错误)

开启交易后,将k1值更改为11,k2值更改为22,但事件导致k2语法错误交易提交失败,k1和k2保留原来的值。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> sets k2 22
(error) ERR unknown command `sets`, with args beginning with: `k2`, `22`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
  • Redis类型错误(运行时错误)

启动事务后,将k1值更改为11,k2值更改为22,但使用k2的类型为List。在运行时检测到拼写错误,最终导致提交交易时出现错误。 ,此时事务不会回滚而是跳过错误命令继续执行。结果,k1 的值发生变化,k2 保持其原始值。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k1 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> lpush k2 22
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>

CAS 操作实现乐观锁定

WATCH 命令可以为 Redis 事务提供检查和设置 (CAS) 行为。

  • CAS?乐观锁?官方的 Redis 示例可以帮助您了解 WATCH 的

键将受到监视,并找出这些键是否发生更改。如果在执行 EXEC 之前至少更改了一个受监视的键,则整个事务将被取消,并且 EXEC 将返回空响应以指示事务已失败。

例如,假设我们需要将一个值自动加 1(假设 INCR 不存在)。

首先我们可以这样做:

val = GET mykey
val = val + 1
SET mykey $val

当只有一个客户端时,上述实现可以很好地工作。然而,当多个客户端同时对同一个密钥执行此类操作时,就会出现竞争条件。例如,如果客户端 A 和 B 都读取了 key 的原始值,比如 10,则两个客户端都会将 key 的值设置为 11,但正确的结果应该是 12。

使用 WATCH 我们可以轻松解决这个问题类问题:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上面的代码时,如果其他客户端在执行WATCH之后、执行EXEC之前更改了mykey的值,那么当前客户端的Transaction将会失败。程序必须做的是不断重试此操作,直到不发生冲突为止。

这种形式的锁定称为乐观锁定,它是一种非常强大的锁定机制。而且由于在大多数情况下,不同的客户端将访问不同的密钥,因此冲突通常很少见,因此通常不需要重试。

  • 实现如何监控时钟?

Redis 使用 WATCH 命令来决定是继续执行还是回滚事务。接下来需要在MULTI之前使用WATCH监控某些键值对,然后使用MULTI命令开启交易,执行数据结构操作的各种命令。 ,此时这些命令正在排队。

当你使用 EXEC 提交事务时,它会首先比较 WATCH 监视的键值对。如果没有变化,则执行交易队列中的命令并发送交易;如果有变化,则不会提交事务。事务回滚时发出任何命令。当然,无论是否回滚,Redis在提交事务之前都会取消WATCH命令。 民工哥死磕Redis教程(六):事务详解

  • watch命令实现监控

在事务开始前使用WATCH监控k1,然后将k1更改为11,表明在事务开始前k1值被更改。MULTI启动事务,将k1值更改为12,k2更改为22,并执行 EXEC ,返回零,表示事务正在回滚;检查k1和k2的值没有被事务中的命令改变。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> WATCH k1
OK
127.0.0.1:6379> set k1 11
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 12
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
  • UNWATCH取消监视
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> WATCH k1
OK
127.0.0.1:6379> set k1 11
OK
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 12
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379> get k1
"12"
127.0.0.1:6379> get k2
"22"
127.0.0.1:6379>

更多Redis学习文章请参见:NoSQL数据库系列中的Redis。该系列持续更新。

Redi 的事务执行阶段

通过上面的命令执行,很明显,Redi 的事务执行分为三个阶段:

  • Open:用 MULTI 启动一个事务
  • 将这些事务命令入队:命令不会被执行收到后立即执行,但会被放入事务队列等待执行
  • 执行:事务由 EXEC 命令触发

当客户端切换到事务状态时,客户端发送的服务器命令将执行不同的操作:

  • 如果客户端发送的命令是 EXEC、DISCARD、WATCH、MULTI 四个命令之一,则服务器立即执行该命令。
  • 相反,如果客户端发送的命令是EXEC、DISCARD、WATCH、MULTI这四个命令之外的命令,那么服务器端不会立即执行该命令,而是将该命令放入事务队列中。然后向客户端返回队列响应。
民工哥死磕Redis教程(六):事务详解

更深入的了解

让我们再过几个问题来更深入地了解Redis事务。

为什么Redis不支持重置?

如果您有使用关系型数据库的经验,那么“Redis 在事务失败时不回滚,而是继续执行剩余命令”的做法对您来说可能会显得有些陌生。

以下是这种方法的优点:

  • Redis 命令只会因语法不正确而失败(并且这些问题在排队期间无法发现),或者命令用在错误类型的 key 上:这也是如此从实际角度来看,失败的命令是由编程错误引起的,这些错误应该在开发过程中检测到,不应该出现在生产环境中。
  • 因为不需要回滚支持,所以Redis的内部可以保持简单和快速。

人们相信 Redis 处理事务的方式会导致错误。但需要注意的是,一般情况下,回滚并不能解决编程错误带来的问题。例如,如果你本来想通过 INCR 命令将某个 key 的值加 1,但不小心加了 2,或者对错误类型的 key 执行了 INCR,回滚就没有办法处理这些情况。

如何理解Redis和事务的ACID?

一般来说,事务有四个属性,称为ACID,分别是原子性、一致性、隔离性和持久性。这是基础,但是很多文章对Redis是否支持ACID存在一些异议。我觉得有必要明确一下:

  • 原子性

首先通过上面我们知道,运行时错误是不会被回滚的。许多文章因此指出Redis事务打破了原子性;而官方文档认为它们符合原子性。

Redis官方文档给出的理解是Redis事务是原子的:所有命令要么全部执行,要么根本不执行。而且并不完全成功。

  • 一致性

Redis事务可以保证命令失败时可以回滚,数据可以恢复到执行前的样子,保证一致性,除非redis进程意外终止。

  • 隔离性

Redis 事务严格遵守隔离性。原因是redis是单线程、单进程模式(v6.0之前),可以保证命令执行过程不被其他客户端命令打断。

但是,Redis 没有像其他结构化数据库那样的隔离级别设计。

  • 持久性

Redis 事务不保证持久性。这是因为redis持久化策略中的RDB和AOF都是异步执行的。出于性能原因,不保证耐用性。

Redis事务的其他实现

  • 基于Lua脚本,Redis可以保证脚本中的命令一次性执行并按顺序执行。它也不提供事务执行错误的回滚。如果某些命令在执行过程中失败,剩余的命令将继续运行直到完成
  • 基于中间标志变量,另一个标志变量用于标识事务是否已完成。读取数据时,首先读取字段变量来判断事务是否完成。但是这样就需要额外的代码来实现,比较麻烦
来源:https://www.pdai.tech/md/db/nosql-redis/db-redis-x-trans.html

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门