首页 > 编程知识 正文

redis事务回滚,redis实例

时间:2023-05-05 22:22:45 阅读:159267 作者:4133

Redis的事务功能,深入了解MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。 Redis事务允许在一个独立的步骤中执行一系列命令,可以保证以下两个重要事项:

Redis序列化并按顺序执行一个事务中的所有命令。 在执行一个Redis事务时,Redis无法插入来自另一个客户端的请求。 这将确保Redis将这些命令作为单独的隔离操作运行。 在Redis事务中,Redis执行其中的所有命令或不执行任何操作。 因此,Redis事务可以保证原子性。 EXEC命令执行事务中的所有命令。 因此,如果客户端正在执行事务,并且在调用MULTI命令之前与Redis服务端断开连接,则不会执行事务中的操作。 相反,如果EXEC命令被调用后与Redis服务器断开连接,则会执行事务中的所有操作。 如果Redis使用仅添加文件(AOF:Append-only File ),则Redis可以确保使用单独的write(2)系统调用将事务写入磁盘。 但是,如果Redis服务器关闭或系统管理员以某种方式停止运行Redis服务进程,则Redis很可能只执行了事务中的某些操作。 Redis在重新启动时检查上述状态,结束执行并输出错误信息。 使用Redis-check-aof工具只能修复上述文件的添加。 此工具将从上述文件中删除未完成执行的事务,以便redis服务可以重新启动。

从2.2版开始,除了上述两种保证外,Redis还可以通过乐观锁定的形式提供更多的保证。 此格式非常类似于“检查重新设置”(CAS:Check And Set )操作。 本文稍后将讨论Redis的乐观锁定。

另一方面,相关指令1. MULTI用于标记事务块的开始。 在使用EXEC命令原子化并执行之前,Redis会对后续命令逐一进行排队。

此命令的执行格式如下:

多重

此命令的返回值是一个简单的字符串,始终OK。

2. EXEC在一个事务中执行以前排队的所有命令,然后恢复正常连接状态。

使用WATCH命令时,EXEC命令仅在受监视的键未更改时执行事务中的命令。 该方式利用了CAS(checkresetsettings )机制。

此命令的执行格式如下:

执行

此命令的返回值是数组,每个元素是原子事务中每个命令的返回值。 使用WATCH命令时,如果事务中止执行,EXEC命令将返回空值。

3. DISCARD会清除以前在一个事务中排队的所有命令,然后恢复正常连接状态。

使用WATCH命令时,DISCARD命令将取消监视当前连接的监视的所有密钥。

此命令的执行格式如下:

DISCARD命令的返回值是一个简单的字符串,始终OK。

4 .如果需要有条件地执行4. WATCH事务处理,请使用此命令将指定的密钥设置为受监视的密钥。

此命令的执行格式如下:

WATCH key [key .]命令的返回值是一个简单的字符串,始终可以。

对于每个键,时间复杂度始终为o(1)。

5. UNWATCH清除以前为一个事务监视的所有密钥。

如果调用了EXEC或DISCARD命令,则不需要手动调用UNWATCH命令。

此命令的执行格式如下:

UNWATCH命令的返回值是一个简单的字符串,始终OK。

时间的复杂性总是o(1)。

二、用法使用MULTI命令可以进入Redis事务。 这个命令的返回值总是OK的。 在这种情况下,用户可以发出多个Redis命令。 Redis将对这些命令进行排队,而不是执行它们。 调用EXEC命令时,Redis会执行事务中的所有命令。

而是调用DISCARD命令将清除事务队列并终止事务。

以下示例按原子公式增加foo和bar联接的值:

正如您在上一会话中看到的,EXEC命令的返回值是数组,每个元素都是事务中每个命令的返回值,返回值的顺序与命令的发出顺序相同。

如果Redis连接位于MULTI请求的上下文中,则通过该连接发出的所有命令的返回值都是QUEUE字符串。 从Redis协议的角度来看,返回值作为状态回复(Status Reply )发送。 调用EXEC命令时,Redis可以轻松地调度事务队列中命令的执行。

三.事务内部错误在一个事务的执行中可能遇到两种类型的命令错误。

在排队时,命令可能会失败。 因此,在调用EXEC命令之前,事务可能会发生错误。 例如,此命令可能存在语法错误(例如,参数数量错误、命令名称错误等)或某些临界条件。)例如,如果使用maxmemory命令为Redis服务器设置内存限制

调用EXEC命令后,事务中的命令将

执行失败。例如,我们对某个键执行了错误类型的操作(例如,对一个字符串(String)类型的键执行列表(List)类型的操作)。

可以使用Redis客户端检测第一种类型的错误,在调用EXEC命令之前,这些客户端可以检查被放入队列的命令的返回值:如果命令的返回值是QUEUE字符串,那么就表示已经正确地将这个命令放入队列;否则,Redis将返回一个错误。如果将某个命令放入队列时发生错误,那么大多数客户端将会中止事务,并且丢弃这个事务。

然而,从Redis 2.6.5版本开始,服务器会记住事务积累命令期间发生的错误。然后,Redis会拒绝执行这个事务,在运行EXEC命令之后,便会返回一个错误消息。最后,Redis会自动丢弃这个事务。

在Redis 2.6.5版本之前,如果发生了上述的错误,那么在客户端调用了EXEC命令之后,Redis还是会运行这个出错的事务,执行已经成功放入事务队列的命令,而不会关心先前发生的错误。从2.6.5版本开始,Redis在遭遇上述错误时,会采用先前描述的新行为,这样便能轻松地混合使用事务和管道。在这种情况下,客户端可以一次性地将整个事务发送至Redis服务器,稍后再一次性地读取所有的返回值。

相反,在调用EXEC命令之后发生的事务错误,Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其他的命令也将会继续执行。

这种行为在协议层面上更加清晰。在以下示例中,当事务正在运行时,有一条命令将会执行失败,即使这条命令的语法是正确的:

 

 

上述示例的EXEC命令的返回值是批量的字符串,包含两个元素,一个是OK代码,另一个是-ERR错误消息。客户端会根据自身的程序库,选择一种合适的方式,将错误信息提供给用户

需要注意的是,即使某个命令执行失败,事务队列中的所有其他命令仍然会执行 —— Redis不会停止执行事务中的命令。

再看另一个示例,再次使用telnet通信协议,观察命令的语法错误是如何尽快报告给用户的:

 

 

这一次,由于INCR命令的语法错误,Redis根本就没有将这个命令放入事务队列。

四、为什么Redis不支持回滚?

如果你具备关系型数据库的知识背景,你就会发现一个事实:在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作,你可能会觉得这种行为很奇怪。

然而,这种行为也有其合理之处:

只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。

对于Redis事务的这种行为,有一个普遍的反对观点,那就是程序有可能会有缺陷(bug)。但是,你应当注意到:事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以我们在开发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。

五、丢弃命令队列

DISCARD命令可以用来中止事务运行。在这种情况下,不会执行事务中的任何命令,并且会将Redis连接恢复为正常状态。示例如下所示:

 

 

六、通过CAS操作实现乐观锁

Redis使用WATCH命令实现事务的“检查再设置”(CAS)行为。

作为WATCH命令的参数的键会受到Redis的监控,Redis能够检测到它们的变化。在执行EXEC命令之前,如果Redis检测到至少有一个键被修改了,那么整个事务便会中止运行,然后EXEC命令会返回一个Null值,提醒用户事务运行失败。

例如,设想我们需要将某个键的值自动递增1(假设Redis没有INCR命令)。

首次尝试的伪码可能如下所示:

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

如果我们只有一个Redis客户端在一段指定的时间之内执行上述伪码的操作,那么这段伪码将能够可靠的工作。如果有多个客户端大约在同一时间尝试递增这个键的值,那么将会产生竞争状态。例如,客户端-A和客户端-B都会读取这个键的旧值(例如:10)。这两个客户端都会将这个键的值递增至11,最后使用SET命令将这个键的新值设置为11。因此,这个键的最终值是11,而不是12。

现在,我们可以使用WATCH命令完美地解决上述的问题,伪码如下所示:

WATCH mykeyval = GET mykeyval = val + 1MULTISET mykey $valEXEC

由上述伪码可知,如果存在竞争状态,并且有另一个客户端在我们调用WATCH命令和EXEC命令之间的时间内修改了val变量的结果,那么事务将会运行失败。

我们只需要重复执行上述伪码的操作,希望此次运行不会再出现竞争状态。这种形式的锁就被称为乐观锁,它是一种非常强大的锁。在许多用例中,多个客户端可能会访问不同的键,因此不太可能发生冲突 —— 也就是说,通常没有必要重复执行上述伪码的操作。

七、WATCH命令详解

那么WATCH命令实际做了些什么呢?这个命令会使得EXEC命令在满足某些条件时才会运行事务:我们要求Redis只有在所有受监控的键都没有被修改时,才会执行事务。(但是,相同的客户端可能会在事务内部修改这些键,此时这个事务不会中止运行。)否则,Redis根本就不会进入事务。(注意,如果你使用WATCH命令监控一个易失性的键,然后在你监控这个键之后,Redis再使这个键过期,那么EXEC命令仍然可以正常工作。)

WATCH命令可以被调用多次。简单说来,所有的WATCH命令都会在被调用之时立刻对相应的键进行监控,直到EXEC命令被调用之时为止。你可以在单条的WATCH命令之中,使用任意数量的键作为命令参数。

当调用EXEC命令时,所有的键都会变为未受监控的状态,Redis不会管事务是否被中止。当一个客户单连接被关闭时,所有的键也都会变为未受监控的状态。

你还可以使用UNWATCH命令(不需要任何参数),这样便能清除所有的受监控键。当我们对某些键施加乐观锁之后,这个命令有时会非常有用。因为,我们可能需要运行一个用来修改这些键的事务,但是在读取这些键的当前内容之后,我们可能不打算继续进行操作,此时便可以使用UNWATCH命令,清除所有受监控的键。在运行UNWATCH命令之后,Redis连接便可以再次自由地用于运行新事务。

如何使用WATCH命令实现ZPOP操作呢?

本文将通过一个示例,说明如何使用WATCH命令创建一个新的原子化操作(Redis并不原生支持这个原子化操作),此处会以实现ZPOP操作为例。这个命令会以一种原子化的方式,从一个有序集合中弹出分数最低的元素。以下源码是最简单的实现方式:

WATCH zsetelement = ZRANGE zset 0 0MULTIZREM zset elementEXEC

如果伪码中的EXEC命令执行失败(例如,返回Null值),那么我们只需要重复运行这个操作即可。

八、Redis脚本和事务

根据定义,Redis脚本也是事务型的。因此,你可以通过Redis事务实现的功能,同样也可以通过Redis脚本来实现,而且通常脚本更简单、更快速。

由于Redis从2.6版本才开始引入脚本特性,而事务特性是很久以前就已经存在的,所以目前的版本才有两个看起来重复的特性。但是,我们不太可能在短时间内移除对事务特性的支持。因为,即使不用求助于Redis脚本,用户仍然能够规避竞争状态,这从语义上来看是适宜的。还有另一个更重要的原因,Redis事务特性的实现复杂度是最小的。

但是,在相当长的一段时间之内,我们不大可能看到整个用户群体都只使用Redis脚本。如果发生这种情况,那么我们可能会废弃,甚至最终移除Redis事务。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。