Redis 事务的基本使用
Redis 的事务操作分别是 multi/exec/discard。multi 指示事务的开始,exec 指示事务的执行,discard 指示事务的丢弃。
> multi
OK
> incr books
QUEUED
> incr books
QUEUED
> exec
(integer) 1
(integer) 2
上面的指令演示了一个完整的事务过程,所有的指令在 exec 之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到 exec 指令,才开执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。因为 Redis 的单线程特性,它不用担心自己在执行队列的时候被其它指令打搅,可以保证他们能得到的「原子性」执行。
QUEUED 是一个简单字符串,同 OK 是一个形式,它表示指令已经被服务器缓存到队列里了。
原子性
上面的例子是事务执行到中间遇到失败了,因为我们不能对一个字符串进行数学运算,事务在遇到指令执行失败后,后面的指令还继续执行,所以 poorman 的值能继续得到设置。
Redis 的事务根本不能算「原子性」,而仅仅是满足了事务的「隔离性」,隔离性中的串行化——当前执行的事务有着不被其它事务打断的权利。
discard
Redis 为事务提供了一个discard 指令,用于丢弃事务缓存队列中的所有指令,在exec 执行之前。
discard 之后,队列中的所有指令都没执行,就好像 multi 和 discard 中间的所有指令从未发生过一样。
使用管道优化事务指令
上面的 Redis 事务在发送每个指令到事务缓存队列时都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络IO 时间也会线性增长。所以通常Redis 的客户端在执行事务时都会结合 pipeline 一起使用,这样可以将多次IO 操作压缩为单次IO 操作。
传送门:《Redis 管道操作的本质》
Watch 机制
Redis 提供的 watch 机制,就是一种乐观锁。
watch 会在事务开始之前盯住 1 个或多个关键变量,当事务执行时,也就是服务器收到了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自watch 之后,是否被修改了(包括当前事务所在的客户端)。如果关键变量被人动过了,exec 指令就会返回 null 回复告知客户端事务执行失败,这个时候客户端一般会选择重试。
当服务器给 exec 指令返回一个 null 回复时,客户端知道了事务执行是失败的,通常客户端 (redis-py) 都会抛出一个 WatchError 这种错误,不过也有些语言 (jedis) 不会抛出异常,而是通过在 exec 方法里返回一个 null,这样客户端需要检查一下返回结果是否为 null 来确定事务是否执行失败。
注:Redis 禁止在 multi 和 exec 之间执行 watch 指令,而必须在 multi 之前做好盯住关键变量,否则会出错。