Redis事务

Wednesday, January 17, 2018

概述

事务可以一次执行多个命令,并且带有以下两个重要保证:

  • 事务是一个单独的隔离操作

事务中的所有命令都会序列化并按顺序执行,事务在执行过程中,不会被其它客户端发送的执行命令打断.

  • 事务是一个原子操作

事务中的命令要么全部被执行,要么全部不执行

EXEC 命令负责出发并执行事务中的所有命令,如果因为其它原因导致 EXEC 没有成功执行,那么事务中的命令都不会执行.

执行过程

一个事务从开始到执行经历了以下三个阶段:

  • 开始事务

使用MULTI命令,将客户端的REDIS_MULTI选项打开,让客户端从非事务状态切换到事务状态.

  • 命令入队

客户端处于非事务状态时,收到命令(MULTI | EXEC | DISCARD | WATCH 四个命令除外)不会立即执行,而是将事务全部放进一个事务队列中,并返回QUEUED,表示命令已入队.

事务队列是一个数组,每个元素包含三个参数:1. 要执行的命令(cmd) 2:命令的参数(argv) 3:参数的个数(argc)

  • 执行事务

使用EXEC触发执行事务中的命令,以先进先出(FIFO)方式执行:最先入队的命令最先执行

执行事务的结果会以 FIFO 的顺序保存到一个回复队列中

事务执行完毕后会将回复队列作为执行结果返回客户端,并将客户端从事务状态切换到非事务状态

事务命令

MULTI | EXEC | DISCARD | WATCH 是 Redis 事务的基础

MULTI

该命令标记事务的开始,Redis 事务是不可嵌套的,当客户端已经处于事务状态时,继续发送 MULTI 命令,服务器会返回一个错误,并不会造成事务的失败,也不会修改队列中已有的数据

127.0.0.1:6379> multi
OK

DISCARD

该命令用于取消一个事务,会清空客户端的整个事务队列,然后将客户端由事务状态切换到非事务状态,最后返回字符串"OK"给客户端.

# 在exec之前行用discard先取消事务,可以看到再执行exec的时候返回了一个错误,因为当前客户端已经不是事务状态.
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set a 3
QUEUED
127.0.0.1:6379> set a 5
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

WATCH

从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS(check-and-set) 操作,WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。

WATCH 命令用于事务开始前监视任意数量的键,如果任意一个键已被其他客户端修改,则事务直接失败.

WATCH 命令只能在客户端进入事务状态之前执行,事务状态下执行会返回一个错误,错误处理和 MULTI 一致.

# 客户端1
127.0.0.1:6379> watch a
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 4
QUEUED
127.0.0.1:6379> exec
(nil)

# 客户端2,在客户端1执行exec前先修改了a的值,导致客户端1的事务执行失败
127.0.0.1:6379> set a 1
OK

UNWATCH

该命令用于手动取消对所有键的监视,没有参数.

对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。

需要注意,Redis 的事务是隔离执行的,所以客户端1上watch了某些key,即便在客户端2上unwatch后修改某些key,在客户端1上的事务也是会失败的.

127.0.0.1:6379> watch a
OK
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 7
QUEUED
127.0.0.1:6379> exec
1) OK

EXEC

该命令触发执行所有事务块内的命令。

假如某个或某些 key 正处于 WATCH 命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么 EXEC 命令只在这个(或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort),即事务中的所有命令都不会执行。

事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。

127.0.0.1:6379> 
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> get a
QUEUED
127.0.0.1:6379> del a
QUEUED
127.0.0.1:6379> exec
1) OK
2) "1"
3) (integer) 1

不支持事务回滚

关系型数据库中,如果事务执行过程中出现异常导致执行失败,会回滚之前已经成功的操作,但Redis不会回滚,官方对此的解释是:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。

Redis Redis

PHP+Redis实现轻量级消息队列Redis发布订阅