redis

Redis学习笔记系列(九)——Redis事务

2020-10-10  本文已影响0人  复旦猿

7. Redis事务

7.1 事务定义

Redis 通过MULTIDISCARDEXECWATCH四个命令来实现事务功能。Redis中的事务同命令一样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行。事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次执行这些命令。下面,我们用一个示例来演示Redis事务。
假设A有10元钱,B有0元钱,A向B转5元钱,那么这个过程就需要用事务来实现,以保证A减5元钱和B增加5元钱要么都成功,要么都失败。

127.0.0.1:6379> SET A 10
OK
127.0.0.1:6379> SET B 0
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCRBY A -5
QUEUED
127.0.0.1:6379> INCRBY B 5
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 5
2) (integer) 5
127.0.0.1:6379> GET A
"5"
127.0.0.1:6379> GET B
"5"

Redis事务可以保证以下两点:

  1. Redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。
  2. Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入。试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。如果不希望发生这种情况,也可以使用事务。

7.2 事务的实现机制

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

7.2.1 开始事务

MULTI命令的执行标记着事务的开始,该命令的唯一作用就是将客户端的REDIS_MULTI选项打开, 让客户端从非事务状态切换到事务状态。

127.0.0.1:6379> MULTI
OK
7.2.2 命令入队

当客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行。但是,当客户端进入事务状态之后,服务器在收到来自客户端的命令时,不会立即执行命令,而是将这些命令全部放进一个事务队列里,然后返回 QUEUED ,表示命令已入队。

127.0.0.1:6379> INCRBY A -5
QUEUED
127.0.0.1:6379> INCRBY B 5
QUEUED
7.2.3 执行事务

前面说到,当客户端进入事务状态之后,客户端发送的命令就会被放进事务队列里。但其实并不是所有的命令都会被放进事务队列, 其中的例外就是 EXECDISCARDMULTIWATCH 这四个命令 —— 当这四个命令从客户端发送到服务器时, 它们会像客户端处于非事务状态一样, 直接被服务器执行。因此,如果客户端正处于事务状态, 那么当 EXEC 命令执行时, 服务器根据客户端所保存的事务队列, 以先进先出(FIFO)的方式执行事务队列中的命令: 最先入队的命令最先执行, 而最后入队的命令最后执行。

127.0.0.1:6379> EXEC
1) (integer) 5
2) (integer) 5

事务状态下,除了EXEC 命令会立刻执行外,DISCARDMULTIWATCH 也会立刻执行。
DISCARD 命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消。
Redis 的事务是不可嵌套的, 当客户端已经处于事务状态, 而客户端又再向服务器发送 MULTI 时, 服务器只是简单地向客户端发送一个错误, 然后继续等待其他命令的入队。 MULTI 命令的发送不会造成整个事务失败, 也不会修改事务队列中已有的数据。
WATCH只能在客户端进入事务状态之前执行, 在事务状态下发送 WATCH命令会引发一个错误, 但它不会造成整个事务失败, 也不会修改事务队列中已有的数据。

7.3 WATCH的作用

WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。示例如下:

# 监控k1
127.0.0.1:6379> WATCH k1
OK
# 开始事务
127.0.0.1:6379> MULTI
OK
# 设置k1的值为v2
127.0.0.1:6379> SET k1 v2
QUEUED
# 设置失败
127.0.0.1:6379> EXEC
(nil)
#  获取k1为v1,显然是别的客户端修改了k1的值
127.0.0.1:6379> GET k1
"v1"

失败原因:

时间 客户端A 客户端B
T1 WATCH k1
T2 MULTI
T3 SET k1 v2
T4 SET k1 v1
T5 EXEC

由于T4时刻,客户端B执行了SET k1 v1,当客户端 A 在 T5 执行 EXEC 时,Redis 会发现 k1 这个被监视的键已经被修改, 因此客户端 A 的事务不会被执行,而是直接返回失败。

7.4 错误处理

7.4.1 语法错误

语法错误指命令不存在或者命令参数的个数不对。跟在MULTI命令后执行了3个命令:一个是正确的命令,成功地加入事务队列;其余两个命令都有语法错误。而只要有一个命令有语法错误,执行EXEC命令后Redis就会直接返回错误,连语法正确的命令也不会执行。比如:

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET k1 v1
QUEUED
127.0.0.1:6379> GETT k1
(error) ERR unknown command `GETT`, with args beginning with: `k1`, 
127.0.0.1:6379> SET k2 v2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> GET k1
(nil)
127.0.0.1:6379> GET k2
(nil)

Redis 2.6.5之前的版本会忽略有语法错误的命令,然后执行事务中其他语法正确的命令。就此例而言,SET k1 v1会被执行,EXEC命令会返回一个结果:1) OK。

7.4.2 运行错误

运行错误指在命令执行时出现的错误,比如使用散列类型的命令操作集合类型的键,这种错误在实际执行之前Redis是无法发现的,所以在事务里这样的命令是会被Redis接受并执行的。如果事务里的一条命令出现了运行错误,事务里其他的命令依然会继续执行(包括出错命令之后的命令),示例如下:

127.0.0.1:6379> SET k1 v1
QUEUED
127.0.0.1:6379> SADD k1 v2
QUEUED
127.0.0.1:6379> GET k1
QUEUED
127.0.0.1:6379> SADD k2 1 2 3
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) "v1"
4) (integer) 3

7.5 Redis事务 V.S. MySQL事务

在传统的关系式数据库中,常常用ACID 性质来检验事务功能的安全性。
Redis 事务保证了其中的一致性(C)和隔离性(I),但并不保证原子性(A)和持久性(D)。

7.5.1 原子性(Atomicity)

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功。
另一方面,如果 Redis 服务器进程在执行事务的过程中被停止 —— 比如接到 KILL 信号、宿主机器停机,等等,那么事务执行失败。
当事务失败时,Redis 也不会进行任何的重试或者回滚动作。

7.5.2 一致性(Consistency)

Redis 的一致性问题可以分为三部分来讨论:运行错误、运行错误、Redis 进程被终结。

7.5.3 隔离性(Isolation)

Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

7.5.4 持久性(Durability)

因为事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,所以事务的持久性由 Redis 所使用的持久化模式决定:

写在最后

如果你觉得我写的文章帮到了你,欢迎点赞、评论、分享、赞赏哦,你们的鼓励是我不断创作的动力~

上一篇 下一篇

猜你喜欢

热点阅读