Redis教程

Redis事务篇

本文主要是介绍Redis事务篇,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

事务

multi,exec,discard和watch是Redis事务的基础。他们能够保证一组命令独立的运行:

一个事务中的命令会被序列化的顺序执行。事务过程中的请求不会被其他的请求中断,这种方式保证了命令执行的独立性。

要么所有命令都成功执行,要么一个都不会执行,Redis事务也具有原子性。exec命令触发执行一个事务内的所有命令,因此如果事务过程中一个客户端在调用exec之前断开了与服务端的连接,那么所有命令都不会执行。换言之,如果调用了exec命令,那么所有的操作都会被执行。当使用append-only file备份时,Redis通过一个独立的write(2)进程将事务写入磁盘。当Redis服务宕机或被管理员强行中断,可能会导致只有一个部分操作被记录了,Redis在重启时会检查这些情况,并报错退出。使用redis-check-aof可以修复append-only备份文件,移除不完整事务让服务器可以顺利重启。

使用

通过multi启动一个Redis事务。该命令总是响应OK。此时用户可以设置多条命令,然而Redis并不会立即执行这些命令,仅将这些命令推入队列中。直到调用exec命令,队列中的命令会被一次性执行完成。调用discard命令会清空事务队列并退出事务。以下foo和bar将会保持原子性:

 

 

 从上图看出,exec的执行结果是一个数组,数组的值和事务队列中的命名一一对应。当redis连接处于multi模式时,所有的命令相应的都是QUEUED。当调用exec时,所有的命令都只做简单的顺序执行。

事务内部错误

在Redis事务过程中可能会产生两种命令错误:

  • 一个命令推送到队列中失败,因此在exec调用之前就会产生一个错误。对于Redis实例来说,可能是语法错误(错误的参数个数,错误的命令名称等),或者是诸如内存溢出的错误(Redis到达了内置限制(maxmemory)上限)。
  • 命令在执行exec失败,例如在执行命令时传递了错误的值(例如:对字符串执行list操作)

客户端通常能够在执行exec通过命令的返回值感知到第一种错:如果命令执行返回的是QUEUED,表示命令时正确的,否则Redis就会返回一个错误。如果在入队列过程中发生错误,大部分客户端将会中断事务并忽该事务。然而从Redis 2.6.5开始,服务端将会记录该错误,然后拒绝执行该事务,并在exec时返回该错误,并自动忽略该错误。

在Redis2.6.5之前这些行为是在执行exec时仅执行已经加入队列中的部分命令,忽略之前的错误。新的行为使事务流程更加简洁,因此整个事务只用发送一次,并且在响应后一次性读取。exec执行后发生的错误没有做特殊的处理:事务的所有其他的命令都会被执行尽管部分命令会执行出错。就像这样:

 

 

exec返回了两个元素,一个OK,一个error。因此客户端需要针对这些信息作出相应的处理。其中最重要的是尽管一个命令失败了,队列中的其他命令都成功执行了,Redis并没有阻止这些正确命令的执行。通过结果我们看到:

 

 

 另外如果是语法性错误:

 

 

 该命令会被拒绝添加到事务队列中。

为什么Redis不支持事务回滚

在前面的示例中,尽管在事务中部分命令可能会执行失败,但是Redis仍然执行了事务,并没有进行回滚,如果有关系型数据库的经验,就会发现这不太符合我们的逻辑。但是对于这样的情况我们有更好的选择来处理:

  • Redis命令会失败的原因只会是语法错误或者Redis键值的数据类型错误,这意味着这类型错误只会是程序本身的错误,这类型的错误很容易在开发阶段就被发现,而不需要等到生产环境来发觉。
  • Redis不需要回滚的能力使Redis内部更加简洁和高效

忽略队列命令

discard命令通常用来中断一个事务,此时所有的命令都不会执行,并且连接的状态也会恢复为常规状态:

 

 

 乐观锁应用

watch通常用来为Redis事务提供CAS行为检查和设置。被watch的键将会被跟踪变化,一旦被watch的键在exec之前发生改变,那么整个事务都会被中断,并且exec会返回一个null通知表示事务失败了。例如:如果我们需要一个键自动增长1(不用incr命令),那么我们可能会尝试这么实现:

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

如果在指定时间内只有单一的客户端来操作,这个操作是比较合理的。但是如果同时有多个客户端尝试增加这个键,就会产生竞争了。例如:A客户端和B客户端都读取了老的值,例如10,这个值将被两个客户端都增加为11,然后最终set命令设置的值也就是11,而不是12。不过幸运的是watch让我们很轻松的解决这类问题:

watch mywatch
val = get mywatch
val = val + 1
multi
set mywatch $val
exec

此时如果有其他客户端事务在本次事务从watch到exec的过程中修改了val,那么本次事务将会失败。我们期望在同一时间反复操作的过程中不会出现新的竞争。这个形式的锁称为乐观锁。乐观锁是一种非常强大的锁形式。在很多应用案例中,多客户端通过不同的键进入,因此不太容易发生冲突 - 通常也就不需要反复操作。

watch解释

那么watch到底是什么呢?这是个让exec变成有条件执行的命令:通知Redis只有在被watch的键没有发生改变的情况下才能执行事务。这些改变来源包括:客户端(写命令),Redis本身(键到期或删除)。如果在watch之后,exec之前,一个键被修改了,那么整个事务都会中断。

注意:在Redis 6.0.9之前,过期的键不会引起事务的中断。事务内部的命令不会处罚watch条件,因为他们只是入队直到exec执行。watch可以被多次调用,对键的监视从watch执行后开始生效直到exec执行后结束。您也可以通过一个watch同时监听多个键。当调用exec后,无论事务是否中断,所有的键都被取消监听。当然客户端断开连接也会导致取消监听。通过unwatch可以刷新释放所有被监听的键。有时候我们通过锁锁住了一些键,但是后来发现这些键不满足我们的需求,此时我们可以通过直接调用unwatch来直接释放被锁住的键。

通过watch实现ZPOP

通过watch实现一个Redis不支持的ZPOP命令(ZPOPMIN,ZPOPMAX和他们的变种已经在Redis 5.0中实现了)是个不错的想法。这个命令主要用于有序集合中原子性的出栈一个score较小的元素。以下是简单实现:

watch myzset
elements = zrange myzset 0 0
multi
zrem myzset elements
exec

如果exec失败(返回null)了,我们只需要重复该操作即可。

Redis脚本和事务

一段Redis脚本默认是事务性的,因此你能够用redis完成的,都可以用redis脚本来实现,通常redis脚本会更简单效率更高。存在这两种方式的原因是脚本是Redis2.6才引入的,在此之前事务已经存在了很长一段时间。

这篇关于Redis事务篇的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!