Redis事务
1. 事务的基本流程
Redis 事务通过 MULTI
、EXEC
、WATCH
等命令实现,底层原理可以分为以下几个步骤:
(1) MULTI 命令
- 当客户端发送
MULTI
命令时,Redis 会将客户端标记为“事务模式”。 - 在事务模式下,客户端发送的所有命令不会立即执行,而是被放入一个队列(命令队列)中。
(2) 命令入队
- 在
MULTI
和EXEC
之间,客户端发送的所有命令都会被追加到事务队列中。 - 这些命令不会立即执行,而是等待
EXEC
命令的触发。
(3) EXEC 命令
- 当客户端发送
EXEC
命令时,Redis 会依次执行事务队列中的所有命令。 - 执行过程中,所有命令是原子的,不会被其他客户端的命令打断。
(4) WATCH 命令
WATCH
命令用于实现乐观锁。- 当客户端对一个或多个键执行
WATCH
后,如果在EXEC
执行之前,这些键被其他客户端修改,则当前事务会失败(返回nil
)
watch我们可以指定监听一个键和多个键,然后exec批量执行
WATCH key [key ...]
2. 事务的原子性
- Redis 事务的原子性是通过单线程模型实现的。
- Redis 是单线程的,所有命令都是顺序执行的。在
EXEC
执行时,事务队列中的命令会连续执行,不会被其他客户端的命令打断。
3. 事务的一致性
- Redis 事务的一致性是通过
WATCH
机制实现的。 - 如果
WATCH
的键在事务执行期间被修改,事务会失败,从而保证数据的一致性。
4. 事务的局限性
- 不支持回滚:如果事务中的某个命令失败,其他命令仍然会执行,Redis 不会自动回滚。
- 部分失败问题:事务中的命令可能会部分成功、部分失败。
- 性能开销:
WATCH
机制会增加额外的性能开销。
Redis 7 对事务的优化
Redis 7 在事务机制上并没有完全改变底层实现,但引入了一些优化和改进:
1. 性能优化
- Redis 7 对事务的执行流程进行了优化,减少了事务模式下的性能开销。
- 通过改进命令队列的处理方式,提高了事务的执行效率。
2. Lua 脚本的增强
- Redis 7 对 Lua 脚本的支持进行了增强,使得 Lua 脚本可以更好地与事务结合使用。
- Lua 脚本在 Redis 7 中的执行效率更高,同时支持更多的 Redis 命令。
3. 更好的错误处理
- Redis 7 改进了事务中的错误处理机制,使得事务失败时的错误信息更加清晰。
- 如果事务中的某个命令失败,Redis 7 会返回更详细的错误信息,方便排查问题。
4. 功能增强
- Redis 7 引入了更多的命令和功能,可以与事务结合使用。
- 例如,Redis 7 支持更多的数据类型和操作,使得事务可以处理更复杂的场景。
Redis 事务的底层实现细节
1. 命令队列
- 在事务模式下,Redis 会为每个客户端维护一个命令队列。
- 所有在
MULTI
和EXEC
之间发送的命令都会被追加到队列中。
2. 事务执行
- 当
EXEC
命令被触发时,Redis 会依次执行命令队列中的所有命令。 - 执行过程中,Redis 会保证命令的原子性,不会被其他客户端的命令打断。
3. WATCH 机制
WATCH
命令会监视一个或多个键。- 如果在
EXEC
执行之前,这些键被其他客户端修改,则当前事务会失败。 WATCH
的实现基于 Redis 的键空间通知机制。
总结
- Redis 事务的底层原理 是基于
MULTI/EXEC/WATCH
机制,通过命令队列和乐观锁实现原子性和一致性。 - Redis 7 在事务机制上进行了性能优化和功能增强,但底层实现并没有本质变化。
- Redis 事务的局限性 包括不支持回滚、部分失败问题和性能开销。
- 如果需要更强大的事务支持,可以结合 Lua 脚本或使用支持 ACID 事务的数据库。
Redis+Lua脚本实现手动回滚补偿
我们每一步执行失败,我们就依次撤销前面的操作
可惜这个并不是真正的acid,我们的mysql执行事务的时候宕机了,它的事务没有提交所以数据并不会进到mysql里面
而redis是人为控制的,所以我们执行lua脚本的时候宕机了,我们之前事务中执行的操作数据仍然进去了,这个是我们无法解决的
local key1 = KEYS[1]
local key2 = KEYS[2]
local key3 = KEYS[3]
local value = ARGV[1]-- 记录原始值
local original_value1 = redis.call('GET', key1)
local original_value2 = redis.call('GET', key2)
local original_value3 = redis.call('GET', key3)-- 第一步操作
redis.call('SET', key1, value)-- 第二步操作
if redis.call('EXISTS', key2) == 0 then-- 手动回滚第一步操作redis.call('SET', key1, original_value1)return "Key2 does not exist"
end
redis.call('SET', key2, value)-- 第三步操作
if redis.call('EXISTS', key3) == 0 then-- 手动回滚前两步操作redis.call('SET', key1, original_value1)redis.call('SET', key2, original_value2)return "Key3 does not exist"
end
redis.call('SET', key3, value)return "Transaction successful"