事务
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。
Redis事务相关命令
-
标记一个事务块的开始。
MULTI
-
取消事务,放弃执行事务块内的所有命令。
DISCARD
-
执行所有事务块内的命令。
EXEC
-
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
WATCH key [key …]
-
取消 WATCH 命令对所有 key 的监视。
UNWATCH
用法
MULTI
命令用于开启一个事务,它总是返回OK
。 MULTI
执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当EXEC
命令被调用时, 所有队列中的命令才会被执行。
当客户端处于事务状态时, 所有传入的命令都会返回一个内容为 QUEUED
的状态回复(status reply), 这些被入队的命令将在 EXEC
命令被调用时执行。
以下是一个事务例子, 它原子地增加了 k1和 k2两个键的值:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>
另一方面, 通过调用 DISCARD
, 客户端可以清空事务队列, 并放弃执行事务。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v11
QUEUED
127.0.0.1:6379> set k2 v22
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379>
通过get k1的返回的值可以发现,事务并没有被执行
事务中出现错误
使用事务时可能会遇上以下两种错误:
-
事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> incr a b c (error) ERR wrong number of arguments for 'incr' command 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k3 (nil) 127.0.0.1:6379>
对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么客户端都会停止并取消这个事务。
-
命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> incr k1 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range 127.0.0.1:6379> get k3 "v3" 127.0.0.1:6379>
EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。
使用 check-and-set 操作实现乐观锁
WATCH
命令可以为 Redis 事务提供 check-and-set (CAS)行为。
被 WATCH
的键会被监视,并会发觉这些键是否被改动过了。 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 WATCH监控的的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。
这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
示例:
新增两个key 表示信用卡可用余额(balance)和欠款金额(debt )
127.0.0.1:6379> set balance 10000
OK
127.0.0.1:6379> set debt 0
OK
事务可以正常执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set balance 9000
QUEUED
127.0.0.1:6379> set debt 1000
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>
当被 WATCH
的监视键被改动过了,那么当前客户端的事务就会失败
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试
注:当 EXEC 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。
Redis 脚本和事务
从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。
因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。
不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。
不过在不远的将来, 可能所有用户都会只使用脚本来实现事务也说不定。 如果真的发生这种情况的话, 那么我们将废弃并最终移除事务功能。