事务可以1次履行多个命令, 并且带有以下两个重要的保证:
事务是1个单独的隔离操作:事务中的所有命令都会序列化、按顺序地履行。事务在履行的进程中,不会被其他客户端发送来的命令要求所打断。
事务是1个原子操作:事务中的命令要末全部被履行,要末全部都不履行。
EXEC 命令负责触发并履行事务中的所有命令:
当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。
但是,如果 Redis http://www.wfuyu.com/server/由于某些缘由被管理员杀死,或遇上某种硬件故障,那末可能只有部份事务命令会被成功写入到磁盘中。
如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那末它会退出,并汇报1个毛病。
使用 redis-check-aof 程序可以修复这1问题:它会移除 AOF 文件中不完全事务的信息,确保http://www.wfuyu.com/server/可以顺利启动。
从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作,具体信息请参考文档的后半部份。
MULTI 命令用于开启1个事务,它总是返回 OK 。
MULTI 履行以后, 客户端可以继续向http://www.wfuyu.com/server/发送任意多条命令, 这些命令不会立即被履行, 而是被放到1个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被履行。
另外一方面, 通过调用 DISCARD , 客户端可以清空事务队列, 并放弃履行事务。
以下是1个事务例子, 它原子地增加了 foo 和 bar 两个键的值:
> MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC 1) (integer) 1 2) (integer) 1
EXEC 命令的回复是1个数组, 数组中的每一个元素都是履行事务中的命令所产生的回复。 其中, 回复元素的前后顺序和命令发送的前后顺序1致。
当客户端处于事务状态时, 所有传入的命令都会返回1个内容为 QUEUED 的状态回复(status reply), 这些被入队的命令将在 EXEC命令被调用时履行。
使用事务时可能会遇上以下两种毛病:
对产生在 EXEC 履行之前的毛病,客户端之前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那末入队成功;否则,就是入队失败。如果有命令在入队时失败,那末大部份客户端都会停止并取消这个事务。
不过,从 Redis 2.6.5 开始,http://www.wfuyu.com/server/会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,谢绝履行并自动放弃这个事务。
在 Redis 2.6.5 之前, Redis 只履行事务中那些入队成功的命令,而疏忽那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包括事务变得简单,由于发送事务和读取事务的回复都只需要和http://www.wfuyu.com/server/进行1次通讯。
至于那些在 EXEC 命令履行以后所产生的毛病, 并没有对它们进行特别处理: 即便事务中有某个/某些命令在履行时产生了毛病, 事务中的其他命令依然会继续履行。
从协议的角度来看这个问题,会更容易理解1些。 以下例子中, LPOP 命令的履行将出错, 虽然调用它的语法是正确的:
Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. MULTI +OK SET a 3 abc +QUEUED LPOP a +QUEUED EXEC *2 +OK -ERR Operation against a key holding the wrong kind of value
EXEC 返回两条批量回复(bulk reply): 第1条是 OK ,而第2条是 -ERR 。 至于怎样用适合的方法来表示事务中的毛病, 则是由客户端自己决定的。
最重要的是记住这样1条, 即便事务中有某条/某些命令履行失败了, 事务队列中的其他命令依然会继续履行 ―― Redis 不会停止履行事务中的命令。
以下例子展现的是另外一种情况, 当命令在入队时产生毛病, 毛病会立即被返回给客户端:
MULTI +OK INCR a b c -ERR wrong number of arguments for 'incr' command
由于调用 INCR 命令的参数格式不正确, 所以这个 INCR 命令入队失败。
如果你有使用关系式http://www.wfuyu.com/db/的经验, 那末 “Redis 在事务失败时不进行回滚,而是继续履行余下的命令”这类做法可能会让你觉得有点奇怪。
以下是这类做法的优点:
有种观点认为 Redis 处理事务的做法会产生 bug , 但是需要注意的是, 在通常情况下, 回滚其实不能解决编程毛病带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不谨慎加上了 2 , 又或对毛病类型的键履行了 INCR , 回滚是没有办法处理这些情况的。
鉴于没有任何机制能避免http://www.wfuyu.com自己酿成的毛病, 并且这类毛病通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。
当履行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出:
redis> SET foo 1 OK redis> MULTI OK redis> INCR foo QUEUED redis> DISCARD OK redis> GET foo "1"
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行动。
被 WATCH 的键会被监视,并会发觉这些键是不是被改动过了。 如果有最少1个被监视的键在 EXEC 履行之前被修改了, 那末全部事务都会被取消, EXEC 返回空多条批量回复(null multi-bulk reply)来表示事务已失败。
举个例子, 假定我们需要原子性地为某个值进行增 1 操作(假定 INCR 不存在)。
首先我们可能会这样做:
val = GET mykey val = val + 1 SET mykey $val
上面的这个实现在只有1个客户真个时候可以履行得很好。 但是, 当多个客户端同时对同1个键进行这样的操作时, 就会产生竞争条件。
举个例子, 如果客户端 A 和 B 都读取了键原来的值, 比如 10 , 那末两个客户端都会将键的值设为 11 , 但正确的结果应当是 12才对。
有了 WATCH , 我们就能够轻松地解决这类问题了:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
使用上面的代码, 如果在 WATCH 履行以后, EXEC 履行之前, 有其他客户端修改了 mykey 的值, 那末当前客户真个事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有产生碰撞为止。
这类情势的锁被称作乐观锁, 它是1种非常强大的锁机制。 并且由于大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况1般都很少, 所以通常其实不需要进行重试。
WATCH 使得 EXEC 命令需要有条件地履行: 事务只能在所有被监视键都没有被修改的条件下履行, 如果这个条件不能满足的话,事务就不会被履行。
如果你使用 WATCH 监视了1个带过期时间的键, 那末即便这个键过期了, 事务依然可以正常履行, 关于这方面的详细情况,请看这个帖子: http://code.google.com/p/redis/issues/detail?id=270
WATCH 命令可以被调用屡次。 对键的监视从 WATCH 履行以后开始生效, 直到调用 EXEC 为止。
用户还可以在单个 WATCH 命令中监视任意多个键, 就像这样:
redis> WATCH key1 key2 key3 OK
当 EXEC 被调用时, 不管事务是不是成功履行, 对所有键的监视都会被取消。
另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对1些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键确当前值是不是符合程序的要求。 当值达不到要求时, 就能够使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。
WATCH 可以用于创建 Redis 没有内置的原子操作。
举个例子, 以下代码实现了原创的 ZPOP 命令, 它可以原子地弹出有序集合中分值(score)最小的元素:
WATCH zset element = ZRANGE zset 0 0 MULTI ZREM zset element EXEC
程序只要重复履行这段代码, 直到 EXEC 的返回值不是空多条回复(null multi-bulk reply)便可。
从定义上来讲, Redis 中的脚本本身就是1种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且1般来讲, 使用脚本要来得更简单,并且速度更快。
由于脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。
不过我们其实不打算在短时间内就移除事务功能, 由于事务提供了1种即便不使用脚本, 也能够避免竞争条件的方法, 而且事务本身的实现其实不复杂。
不过在不远的将来, 可能所有用户都会只使用脚本来实现事务也说不定。 如果真的产生这类情况的话, 那末我们将废弃并终究移除事务功能。
上一篇 《程序员跳槽全攻略》读后总结
下一篇 jquery-osx