1. 绪言
Redis也提供了事务机制,可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。但Redis对事务的支持是部分支持,不想关系型数据库,要么都成功要么都失败,Redis可以部分成功部分失败。本篇中,我们来详细所以说redis那些事。
2. Redis事务机制
2.1 事务流程
Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行,要么都成功,要么都失败。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。一个事务从开始到执行会经历以下三个阶段:
1)开始事务;
2)命令入队;
3)执行事务。
从上图输出中可以看到,当输入MULTI命令后,服务器返回OK表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不会马上执行,而是返回”QUEUED”,这表示命令已经被服务器接受并且暂时保存起来,最后输入EXEC命令后,本次事务中的所有命令才会被依次执行,可以看到最后服务器一次性返回了三个OK,这里返回的结果与发送的命令是按顺序一一对应的,这说明这次事务中的命令全都执行成功了。
2.2 事务命令
在上一小节中,我们使用了MULTI命令和EXEC命令。MULTI命令标志着事务的开始,EXEC命令开始执行事务。从上一小节图片中我们也可以看出,事务中的命令要全部执行完之后才能获取每个命令的结果,但是如果一个事务中的命令B依赖于他上一个命令A的结果的话该怎么办呢?例如电商系统在抢购业务中,先要获取当前库存,才能在当前库存的基础上进行其他操作。这种场合仅仅使用上面介绍的MULTI和EXEC是不能实现的,因为MULTI和EXEC中的命令是一起执行的,并不能将其中一条命令的执行结果作为另一条命令的执行参数。这时候就要用到redis事务机制中的其他命令,下面列出了redis事务机制中所有命令:
命令原型 |
时间复杂度 |
命令描述 |
返回值 |
MULTI |
用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行。 |
始终返回OK |
|
EXEC |
执行在一个事务内命令队列中的所有命令,同时将当前连接的状态恢复为正常状态,即非事务状态。如果在事务中执行了WATCH命令,那么只有当WATCH所监控的Keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。 |
原子性的返回事务中各条命令的返回结果。如果在事务中使用了WATCH,一旦事务被放弃,EXEC将返回NULL-multi-bulk回复。 |
|
DISCARD |
回滚事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如果WATCH命令被使用,该命令将UNWATCH所有的Keys。 |
始终返回OK。 |
|
WATCH key [key ...] |
O(1) |
在MULTI命令执行之前,可以指定待监控的Keys,然而在执行EXEC之前,如果被监控的Keys发生修改,EXEC将放弃执行该事务队列中的所有命令。 |
始终返回OK。 |
UNWATCH |
O(1) |
取消当前事务中指定监控的Keys,如果执行了EXEC或DISCARD命令,则无需再手工执行该命令了,因为在此之后,事务中所有被监控的Keys都将自动取消。 |
始终返回OK。 |
下面通过代码尝试使用上述几个命令:
1)正常执行
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key01 a QUEUED 127.0.0.1:6379> SET key02 b QUEUED 127.0.0.1:6379> GET key01 QUEUED 127.0.0.1:6379> SET key03 c QUEUED 127.0.0.1:6379> EXEC 1) OK 2) OK 3) "a" 4) OK
2) 取消事务
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key01 a QUEUED 127.0.0.1:6379> SET key02 b QUEUED 127.0.0.1:6379> DISCARD OK 127.0.0.1:6379> GET key01 (nil)
可以看到,执行DISCARD命令后,返回了OK,事务被取消,所以再次GET key01的时候返回了nil。
3)WATCH
命令按图示箭头方向顺序输入并执行,在左侧窗口中用WATCH命令监视key01,然后MULTI命令开始后,在右侧窗口更改了key02的值,所以左侧窗口执行EXEC命令后,返回nil,事务执行失败,事务中的INCR key01 , SET key02 1两条命令都没有执行,所以最后获取key02返回的值是nil,而key01的值也是右侧窗口的赋值。
4)UNWATCH
按图示箭头顺序输入并执行命令,WATCH监视key01后,用UNWATCH接触监视,开始MULTI事务后,在右侧窗口改变key01的值,然后左侧窗口继续执行事务,发现事务正常执行,事务中获取到的key01的值是在右侧窗口赋值的基础上加1,key02也成功创建。
2.3 Redis事务中的错误
先来看如下两块代码:
代码一:
在上述代码块中,先给key01赋一个字符串值,然后在事务中进行整数运算,显然是有误的,但是整个事务除了数值运算那个命令其他命令都成功运行。
代码块二:
在上述代码块中,事务中出现拼写错误,执行事务后,直接提示失败,没有任何返回值,可以发现,事务中所有命令都没有执行。
对比上述两个代码块,为什么一个事务成功执行,一个事务执行失败呢?这就涉及到redis事务中的两类失败:
1)运行错误: 运行错误表示命令在执行过程中出现错误,比如用GET命令获取一个散列表类型的键值、对字符型进行数字运算等。这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令会被Redis接受并执行。如果事务里有一条命令执行错误,其他命令依旧会执行(包括出错之后的命令)。
2)语法错误:就像上面的例子一样,语法错误表示命令不存在或者参数错误例如参数的数量错误、命令名称错误,这种情况需要区分Redis的版本,Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行,就比如上面的例子。这种错误会导致事务执行失败,事务中所有命令都执行失败。
3. 总结
本篇介绍了redis中的事务机制,但关于分布式锁的部分并未涉及,之后再补充。
参考:
https://www.cnblogs.com/Jason-Xiang/p/5364252.html
https://blog.****.net/qq_37169817/article/details/78839774