Redis事务实现
Redis 的事务实现与传统关系型数据库(如 MySQL)有根本性的不同。它不保证严格的原子性(Atomicity),也没有回滚(Rollback)机制。其核心思想是将多个命令打包,然后顺序地执行,并保证在执行期间不会被其他客户端的命令打断。
Redis 事务的实现主要依赖于以下几个命令和机制:
核心命令
MULTI:标记一个事务块的开始。随后的命令都不会立即执行,而是被服务器放入一个队列中。EXEC:执行事务队列中的所有命令,并返回所有命令的返回值。DISCARD:取消事务,清空事务队列。WATCH:监视一个或多个 key。如果在EXEC命令执行前,这些被监视的 key 被其他客户端修改,那么整个事务将被取消(EXEC返回nil表示事务失败)。UNWATCH:取消所有WATCH命令对 key 的监视。
事务执行的三阶段
一个完整的 Redis 事务过程可以分为三个阶段:
事务开始 (
MULTI):- 客户端执行
MULTI命令,服务器会将此客户端的状态从非事务状态切换到事务状态。之后,除了EXEC,DISCARD,WATCH,MULTI这几个命令外,其他命令都不会立即执行。
- 客户端执行
命令入队:
- 当客户端处于事务状态时,它发送的正常命令(如
SET,GET,SADD等)不会被立即执行,而是被服务器放入一个名为 事务队列(FIFO队列) 的数组中。
- 当客户端处于事务状态时,它发送的正常命令(如
事务执行 (
EXEC) 或 丢弃 (DISCARD):EXEC:服务器收到EXEC命令后,会遍历事务队列,依次执行其中的所有命令,并将所有结果收集起来,一次性返回给客户端。在此执行期间,Redis 服务器不会处理其他客户端的任何请求,从而保证了这些命令被隔离(Isolation)地、不间断地执行。DISCARD:服务器收到DISCARD命令后,会清空事务队列,并将客户端状态切换回非事务状态。
为什么没有回滚?
这是 Redis 事务最显著的特点。Redis 在设计上选择了一种更简单、更高效的方式:
- 语法错误(入队错误):如果一个命令在入队时就被检测出有语法错误(例如命令名写错、参数个数不对),那么整个事务在
EXEC时都不会被执行。EXEC会直接返回错误。 - 运行错误:如果一个命令在入队时语法正确,但在
EXEC运行时出错(例如,对字符串类型的 key 执行SADD操作),Redis 不会停止事务,也不会回滚之前已执行的命令,它会继续执行队列中的后续命令。 - 设计哲学:Redis 的作者认为,Redis 事务中的错误通常都是编程错误(比如用错了命令类型),这种错误应该在开发阶段被发现,而不是在生产环境通过回滚来挽救。省略回滚功能使得 Redis 内部实现更简单、速度更快。
实现乐观锁的 WATCH 命令
WATCH 机制是 Redis 实现 乐观锁(Optimistic Locking) 的关键,用于解决事务的并发问题。
原理:
- 每个 Redis 数据库都维护一个
watched_keys字典,它记录了哪个客户端监视了哪个 key。 - 当客户端
WATCH一个 key 时,它就会记录当前 key 的版本号(实际上是一个全局递增的计数器)。 - 在任何客户端修改了一个 key 之后,这个 key 的版本号就会增加。
- 当执行
EXEC时,服务器会检查所有被WATCH的 key 的版本号是否自WATCH以来发生过变化。 - 只要有一个 key 的版本号变化了,就说明有其他客户端修改过这个数据,当前客户端的事务安全性已被破坏。此时服务器会拒绝执行事务(
EXEC返回nil)。
- 每个 Redis 数据库都维护一个
流程:
1
2
3
4
5
6客户端 A> WATCH balance
客户端 A> MULTI
客户端 A> DECRBY balance 100
(此时客户端 B 执行了 `SET balance 1000`,修改了 balance)
客户端 A> EXEC
(返回 nil,因为 WATCH 的 key 被修改了,事务执行失败)客户端 A 需要做的就是重试这个事务(重新
WATCH并执行流程)。
与 Lua 脚本的对比
Redis 2.6 之后引入了 Lua 脚本,它提供了另一种更强大的事务性执行方式:
- 更强的一致性:整个脚本在执行时是以原子方式进行的,中途绝不会被其他命令打断,无需使用
WATCH。 - 更灵活:可以在脚本中实现复杂的逻辑和计算。
- 因此,对于复杂的多命令操作,Lua 脚本通常是比
MULTI/EXEC事务更推荐的选择。
总结
| 特性 | Redis 事务实现方式 |
|---|---|
| 原子性 | 部分原子性:要么全部执行,要么全部不执行(仅在入队错误时)。运行时错误不会中断和回滚。 |
| 隔离性 | 严格隔离:EXEC 执行期间,命令队列是串行化执行的,不会被中断。 |
| 回滚 | 不支持。 |
| 一致性 | 通过 WATCH 实现的乐观锁 来保证在并发环境下数据的一致性。 |
| 用途 | 适合用于需要一次性、按顺序执行多个命令,且不希望中途被打断的场景。对于需要回滚或复杂逻辑的场景,应优先选用 Lua 脚本。 |
简单来说,Redis 事务的本质是 命令的批量打包和顺序执行,配合 WATCH 实现了一种乐观锁的并发控制机制。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论


