文章目录
1 关于Redis事务
1.1 官网介绍
Redis事务(transaction)允许在单个步骤中执行一组命令,并保证了以下两点:
- 事务中的所有命令都将被序列化并按照顺序执行。
- 在事务执行的过程中,Redis不会处理其他客户端发送的请求,这保证了这些命令将被当作单个独立操作执行。
EXEC
命令将触发事务中所有命令的执行。- 如果客户端在调用
EXEC
之前丢失了服务器连接,事务中的所有命令都不会被执行。 - 相反,如果调用了
EXEC
,事务中的所有命令都将被执行。 - 当使用AOF(append-only file)持久化方式时,Redis会确保使用单个写入操作来将事务中的命令保存到磁盘上。
但是,如果 Redis 服务器崩溃或被系统管理员以某种方式终止,则可能只写入了部分操作。Redis 将在重新启动时检测到这种情况,并会报错退出。使用 redis-check-aof 工具可以修复仅附加文件(删除部分事务),以便服务器可以重新启动。
- 如果客户端在调用
从Redis 2.2版本开始,Redis使用乐观锁的形式(以类似与CAS的方式)对以上两点提供了额外的保证。
1.2 解释
基本上每种数据库都有自己的数据库事务控制机制,而Redis是将一组命令放到事务中序列化、按顺序一次执行,在执行过程中其他客户端的命令都必须等待事务执行完成。
2 事务操作
2.1 事务命令
Redis事务操作主要涉及以下几个命令:
MULTI
:标记事务块的开始,后续输入的命令都将排队等待执行。DISCARD
:刷新(Flush)事务中排队的所有命令,并将连接从事务状态恢复到正常状态。如果使用了WATCH
命令,DISCARD
将取消监视所有被连接(connection)监视(watch)的键。EXEC
:执行事务中所有排队的命令,并将连接从事务状态恢复到正常状态。如果使用了WATCH
命令,则只会在所有被监视的键都没有被修改的情况下才会执行。WATCH
:监视指定的键,以实现事务的有条件执行。- WATCH 用于为 Redis 事务提供检查和设置 (CAS) 行为。
- 取消监视键的方式有如下几种
- 调用EXEC,不论是否成功,都将取消对所有键的监视。
- 调用DISCARD,将在丢弃事务的同时,取消对所有键的监视。
- 使用UNWATCH命令,取消对所有键的监视。
- 监视键是为了检测执行前被监视的键是否被修改,如果在EXEC之前,有任何一个被监视的键发生了变动(这包括客户端所做的修改(例如写入命令)以及 Redis 本身所做的修改(例如过期或驱逐)),那么整个事务都将被中止,EXEC将返回nil。
在 6.0.9 之前的 Redis 版本中,过期的密钥不会导致事务中止
事务中的命令不会触发 WATCH 条件,因为它们只会排队直到发送 EXEC。
2.3 操作示例
示例一:执行事务
使用MULTI
开启事务,输入要执行的命令后,使用EXEC
来一次执行。
示例二:丢弃事务
使用MULTI开启事务,输入命令后,使用DISCARD
来丢弃事务。
示例三:监视key无变化
使用WATCH来监视一个key,在事务执行前,被监视的key无变化。
示例四:监视key被修改
使用WATCH来监视一个key,在事务执行前,key被修改。
2.3 错误处理
在Redis事务中,可能出现以下两种错误的情况:
- 命令排队失败
- 命令语法错误
- 一些严重情况,例如内存不足的情况(如果服务器使用 maxmemory 指令配置为具有内存限制)。
- 命令执行失败
- 例如,我们对键执行了错误的操作(如对字符串值调用列表操作)。
2.3.1 命令排队失败的处理
从Redis 2.6.5
开始,如果Redis命令排队过程中检查到错误,将打印错误信息,此后输入的正常命令也会回复“QUEUED”;但是在执行exec命令时,Redis将拒绝执行该事务,返回错误信息并丢弃该事务。
在
Redis 2.6.5
之前,客户端需要通过检查排队命令的返回值来检测之前发生的错误:如果命令回复 QUEUED,则说明已正确排队,否则 Redis 返回错误。如果在排队命令时出现错误,大多数客户端将中止并丢弃事务。否则,如果客户端选择继续处理该事务,则该EXEC命令将成功执行排队的所有命令,而不管以前的错误如何。
示例:
> multi ## 开始事务
"OK"
> get name
"QUEUED"
> set name qing.he
"QUEUED"
> ssss name s ## 语法错误,回复错误信息
"ERR unknown command 'ssss'"
> get name ## 后续正常命令,还是回复QUEUED
"QUEUED"
> exec ## 拒绝执行、报错、丢弃事务
"EXECABORT Transaction discarded because of previous errors."
2.3.2 命令执行失败的处理
如果命令排队过程中没有错误,那么调用exec命令后出现的错误将不会被特别处理:即使事务中某些命令在执行过程中出现错误,所有其他的命令依旧会被正常执行。
为什么不回滚呢?
官网对此的解释是:
Redis does not support rollbacks of transactions since supporting rollbacks would have a significant impact on the simplicity and performance of Redis.
(Redis不支持事务的回滚,因为回滚将对Redis的简单性和高性能造成重大影响。)
命令执行失败也可大致分为以下两类:
- 命令本身执行报错:如对键执行了错误的操作(如对字符串值调用列表操作)。
- Redis故障:EXEC执行过程中,Redis发生故障。
Redis 将在重新启动时检测到这种情况,并会报错退出。使用 redis-check-aof 工具可以修复仅附加文件(删除部分事务),以便服务器可以重新启动。
示例:
> multi
"OK"
> get name
"QUEUED"
> set name mr.x
"QUEUED"
> incr name ## 这个命令在执行过程中报错
"QUEUED"
> get name
"QUEUED"
> exec
1) "qing.he" # 错误命令之前的命令正常执行
2) "OK" # 错误命令之前的命令正常执行
3) "ReplyError: ERR value is not an integer or out of range" # 错误命令执行报错
4) "mr.x" # 错误命令之后的命令正常执行
> get name # 验证一下,其他命令确实成功了
"mr.x"
3 Redis事务的ACID特性分析
3.1 ACID
先温习一下数据库事务的ACID:
- 原子性(Atomicity):原子性要求事务中的所有操作要么全部成功执行,要么全部失败回滚,不允许部分操作成功部分操作失败的情况发生。
- 一致性(Consistency):一致性要求事务在执行前后数据库应该保持一致状态,即事务执行后数据库应该从一个一致状态转换到另一个一致状态,不会破坏数据库的完整性约束。
- 隔离性(Isolation):隔离性指多个事务并发执行时,每个事务的操作应该被隔离开,不会相互影响,即每个事务应该感觉不到其他事务的存在。
- 持久性(Durability):持久性要求事务一旦提交后,其所做的修改应该永久保存在数据库中,即使系统崩溃或发生故障,数据也不应该丢失。
由于Redis事务执行过程中可能出现上文中提到的几种错误,因此对Redis事务是否能够保证ACID特性的分析,也需要根据场景分别讨论。
3.2 A原子性分析
- 正常情况下
Redis事务中的所有命令都将被序列化并按照顺序执行,能够保证原子性。 - 命令排队失败
事务中所有的命令都会被丢弃,所以能够保证原子性。 - 命令执行失败
即使事务中某些命令在执行过程中出现错误,所有其他的命令依旧会被正常执行,因此不能保证原子性。 - EXEC过程中Redis故障
如果Redis使用了AOF,将在重新启动时检测到这种情况,并会报错退出。使用 redis-check-aof 工具可以修复仅附加文件(删除部分事务)。这种情况下也可以保证原子性。
3.3 C一致性分析
- 正常情况下
Redis事务中的所有命令都将被序列化并按照顺序执行,能够保证一致性。 - 命令排队失败
事务中所有的命令都会被丢弃,所以能够保证一致性。 - 命令执行失败
有错误的命令不会被执行,正确的命令可以正常执行,也不会改变数据库的一致性。 - EXEC过程中Redis故障
在这种情况下,实例故障后会进行重启,这就和数据恢复的方式有关了,我们要根据实例是否开启了 RDB 或 AOF 来分情况讨论下。-
如果我们没有开启 RDB 或 AOF,那么,实例故障重启后,数据都没有了,数据库是一致的。
-
如果我们使用了 RDB 快照,因为 RDB 快照不会在事务执行时执行,所以,事务命令操作的结果不会被保存到 RDB 快照中,使用 RDB 快照进行恢复时,数据库里的数据也是一致的。
-
如果我们使用了 AOF 日志,而事务操作还没有被记录到 AOF 日志时,实例就发生了故障,那么,使用 AOF 日志恢复的数据库数据是一致的。如果只有部分操作被记录到了 AOF 日志,我们可以使用 redis-check-aof 清除事务中已经完成的操作,数据库恢复后也是一致的。
-
3.4 I隔离性分析
- 在EXEC之前,需要使用WATCH来保证事务的隔离性。
- 在EXEC之后,由于Redis在事务执行过程中,不会处理其他客户端的请求,因此可以保证隔离性。
3.5 D持久性分析
- 如果Redis没有配置RDB或AOF持久化,那么肯定时不满足事务的持久性(因为根本不会持久化)。
- 如果Redis使用了RDB模式,那么在一个事务执行之后,下一次的RDB快照执行前,如果发生了Redis故障,就可能造成事务数据丢失。因此,也不能保证事务的持久性。
- 如果Redis使用了AOF模式,由于AOF模式的三种配置选项no、everysec、always都存在数据丢失的可能,所以也不能保证持久性。