一、Redis事务讲解与使用
这篇文章可以让我们很好的了解Redis的事务特性和原理,以便我们可以更好的使用它。
multi、exec、discard 和watch是 Redis 中事务的基础指令。它们允许在一个步骤中执行一组命令,有两个重要保证:
- 事务中的所有命令都被序列化并按顺序执行。在Redis 事务的执行过程中,永远不会执行另一个客户端发出的请求。这保证了命令作为单个隔离操作执行。
- 要么处理所有命令,要么不处理任何命令,因此 Redis 事务也是原子的。使用exec命令触发事务中的所有命令的执行。因此,如果客户失去了在事务的上下文中的服务器的连接调用后再执行exec的命令则不会被执行;而不是如果EXEC命令被调用时,执行所有操作。使用append-only file(附加文件) 时,Redis 将确保使用单个write系统调用将事务写入磁盘。但是,如果 Redis 服务器崩溃或被系统管理员以某种方式杀死,则可能只注册了部分操作。Redis 将在重新启动时检测到这种情况,并会出现错误退出。使用该redis-check-aof工具可以修复将删除部分事务的仅附加文件,以便服务器可以重新启动。
从 2.2 版开始,Redis 允许对上述两个提供额外的保证,以一种非常类似于检查和设置 (CAS) 操作的方式采用乐观锁定的形式。这将在本页稍后记录。
1.1用法
使用multi命令将开启 Redis 事务。该命令始终以回复OK。此时用户可以发出多个命令。Redis 不会执行这些命令,而是将它们排入队列。调用exec 后,所有命令都会执行。在执行exec之前调用 discard 将清空事务队列中的命令并退出事务,在exec执行后再调用的discard将会提示multi不存在。
示例:
从上面的会话中可以看出,EXEC返回一个回复数组,其中每个元素都是事务中单个命令的回复,与命令发出的顺序相同。
当 Redis 连接处于MULTI请求的上下文中时,所有命令都将回复字符串 queued(从 Redis 协议的角度来看,作为状态回复发送)。排队的命令只是在调用EXEC时安排执行。
1.2 事务执行发生错误
在事务期间可能会遇到两种命令错误:
- 命令可能无法排队,因此在调用EXEC之前可能会出现错误。例如,命令可能在语法上是错误的(参数数量错误,命令名称错误,…),或者可能存在一些关键情况,例如内存不足情况(如果服务器被配置为使用maxmemory指令具有内存限制))。
- 调用EXEC后, 命令可能会失败,例如,我们对具有错误值的键执行了操作(例如对字符串值调用列表操作)。
客户端过去常通过检查排队命令的返回值来感知第一种错误,发生在EXEC调用之前:如果命令回复 QUEUED,则它已正确排队,否则 Redis 返回错误。如果在排队命令时出现错误,大多数客户端将中止事务并丢弃它。
但是从 Redis 2.6.5 开始,服务器会记住命令累积过程中出现的错误,并拒绝执行事务并在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
-ERR Operation against a key holding the wrong kind of value
EXEC返回两个元素的批量字符串回复,其中一个是OK代码,另一个是-ERR回复。客户端库需要找到一种合理的方式来向用户提供错误。
需要注意的是,即使一个命令失败,队列中的所有其他命令也会被处理——Redis不会停止命令的处理。
另一个示例,再次使用带有 的有线协议telnet,显示了如何尽快报告语法错误:
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command
这次由于语法错误,错误的INCR命令根本没有排队。
1.3为什么Redis不支持回滚?
如果您有关系数据库背景,Redis 命令可能会在事务期间失败,但 Redis 仍会执行事务的其余部分而不是回滚,这一事实对您来说可能看起来很奇怪。
但是,对这种行为有很好的意见:
Redis 命令只有在使用错误的语法调用时才会失败(并且在命令排队期间无法检测到问题),或者针对持有错误数据类型的键:这意味着实际上失败的命令是编程错误的结果,一种很可能在开发过程中检测到的错误,而不是在生产中。
Redis 内部简化,速度更快,因为它不需要回滚的能力。
反对 Redis 观点的一个论点是会发生错误,但是应该注意的是,通常回滚不会使您免于编程错误。例如,如果查询将键增加 2 而不是 1,或者增加错误的键,则回滚机制无法提供帮助。鉴于没有人可以将程序员从他或她的错误中拯救出来,而且 Redis 命令失败所需的那种错误不太可能进入生产环境,我们选择了不支持错误回滚的更简单、更快的方法。
1.4 使用检查和设置的乐观锁定
WATCH用于为 Redis 事务提供检查和设置 (CAS) 行为。 WATCHed 键被监视以检测对它们的更改。如果在EXEC命令之前至少修改了一个被监视的键,则整个事务中止,并且EXEC返回Null 回复以通知事务失败。
例如,假设我们需要以原子方式将键的值递增 1(假设 Redis 没有INCR)。
感谢WATCH,我们能够很好地对问题建模:
WATCH mykey //mykey即是被监控的键 watched
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代码,如果存在竞争条件并且另一个客户端val在我们调用WATCH和调用EXEC之间的时间内修改了结果,则事务将失败。
我们只需要重复操作,希望这次我们不会有新的竞争。这种锁定形式称为乐观锁定,是一种非常强大的锁定形式。在许多用例中,多个客户端将访问不同的密钥,因此不太可能发生冲突——通常不需要重复操作。
1.5 手表解释
那么WATCH到底是关于什么的呢?这是一个使EXEC 成为条件的命令:我们要求 Redis 只有在没有WATCH修改任何ed 键时才执行事务。这包括客户端所做的修改,如写入命令,以及 Redis 本身所做的修改,如过期或驱逐。如果密钥在被WATCH编辑和收到EXEC之间被修改 ,整个事务将被中止。
注意 * 在 6.0.9 之前的 Redis 版本中,过期的密钥不会导致事务中止。更多关于此 * 事务中的命令不会触发WATCH条件,因为它们只会在EXEC发送之前排队。
也可以使用UNWATCH命令(不带参数)来刷新所有被监视的键。有时这很有用,因为我们乐观地锁定了几个键,因为我们可能需要执行事务来更改这些键,但是在读取键的当前内容后我们不想继续。发生这种情况时,我们只需调用 UNWATCH,这样连接就可以自由地用于新事务。
上一篇:浅谈Redis
下一篇:揭秘Redis持久化