准确的讲,Redis 事务包含两种模式 : 事务模式 和 Lua 脚本。
先说结论:
Redis 的事务模式具备如下特点:
- 保证隔离性;
- 无法保证持久性;
- 具备了一定的原子性,但不支持回滚;
- 一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。
但 Lua 脚本更具备实用场景,它是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua 脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果。
Lua 脚本模式的身影几乎无处不在,比如分布式锁、延迟队列、抢红包等场景。
1 事务原理
Redis 的事务包含如下命令:
序号 | 命令及描述 |
---|---|
1 | MULTI 标记一个事务块的开始。 |
2 | EXEC 执行所有事务块内的命令。 |
3 | DISCARD 取消事务,放弃执行事务块内的所有命令。 |
4 | WATCH key [key ...] 监视一个 (或多个) key ,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断。 |
5 | UNWATCH 取消 WATCH 命令对所有 key 的监视。 |
事务包含三个阶段:
- 事务开启,使用 MULTI , 该命令标志着执行该命令的客户端从非事务状态切换至事务状态 ;
- 命令入队,MULTI 开启事务之后,客户端的命令并不会被立即执行,而是放入一个事务队列 ;
- 执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行 ,如果是 DISCARD 则事务被丢弃。
下面展示一个事务的例子。
redis> MULTI
OK
redis> SET msg "hello world"
QUEUED
redis> GET msg
QUEUED
redis> EXEC
1) OK
1) hello world
这里有一个疑问?在开启事务的时候,Redis key 可以被修改吗?
在事务执行 EXEC 命令之前 ,Redis key 依然可以被修改。
在事务开启之前,我们可以 watch 命令监听 Redis key 。在事务执行之前,我们修改 key 值 ,事务执行失败,返回 nil 。
通过上面的例子,watch 命令可以实现类似乐观锁的效果 。
2 事务的 ACID
2.1 原子性
原子性是指:一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
第一个例子:
在执行 EXEC 命令前,客户端发送的操作命令错误,比如:语法错误或者使用了不存在的命令。
redis> MULTI
OK
redis> SET msg "other msg"
QUEUED
redis> wrongcommand ### 故意写错误的命令
(error) ERR unknown command 'wrongcommand&