redis lua

很多人会说 redis 执行 lua 是原子性的,但这样说不太准确。

执行隔离性(Isolation Guarantee)

redis 执行命令是单线程的,执行 lua 脚本时会阻塞其他指令和脚本的执行。

这确保了脚本在执行的过程中不会被并发操作干扰(例如同时修改一个数据)。

请看如下无执行隔离的时序图示例:

时序 客户端A 客户端B
t1 客户端A 执行 GET token:42→ 返回存在
t2 客户端B 执行 GET token:42→ 返回存在
t3 客户端A 执行 DEL token:42(成功)
t4 客户端B 执行 DEL token:42(因 token 已被A删除,此操作实际无效,但B仍会继续下单)

可见A和B都成功执行了下单操作,出现重复下单错误。

再看如下有执行隔离的时序图示例:

时序 客户端A 客户端B Lua
t1 发送 EVAL "if redis.call('GET', KEYS[1]) then return redis.call('DEL', KEYS[1]) else return 0 end" 1 token:42
t2 发送 EVAL "if redis.call('GET', KEYS[1]) then return redis.call('DEL', KEYS[1]) else return 0 end" 1 token:42
t3 执行客户端A的脚本: 1. GET token:42(存在) 2. DEL token:42(成功删除)
t4 客户端B 的请求此时才被处理。执行同样的脚本: 1. GET token:42(已不存在) 脚本返回 0,拒绝操作。

可见只有A成功了,B的操作失败了,未出现重复下单错误。

不满足全有或全无原则(all-or-nothing)

all-or-nothing
指事务要么完全发生,要么根本不发生。例如A向B转账100元,B已收到100元,但A并非扣除100元,这是不可接受的。

原子提交/回滚机制(Atomic Commit/Rollback)

在数据库的实现中一般会使用原子提交/回滚实现全有或全无原则。

原子提交

当事务中的所有操作都成功执行,并且验证没有违反约束时,系统会 一次性把事务的所有修改写入数据库(这一批操作完成了)。

提交之后,所有事务产生的更改对其他事务可见。

原子回滚

如果事务中某个环节失败,或者用户/系统主动要求撤销,就必须 撤回事务中已做的所有修改,让数据库回到事务开始之前的状态(这一批已做的操作都不算数)。

一般会使用 undo log 来实现,保证把执行的操作撤销掉。

请保持脚本的轻量

由于脚本执行期间会阻塞整个 Redis 的执行,避免在脚本中编写复杂循环或耗时操作是关键。否则会严重影响 Redis 的响应性能。