Lua 解决 Redis 缓存原子性问题

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua 解决 Redis 缓存原子性问题相关的知识,希望对你有一定的参考价值。

文章目录

Redis 事务局限性

redis的缺点主要体现在并不支持完整的ACID事务,redis虽然提供事务功能,但redis的事务和关系数据库的事务不可相类比,redis的事务只能保证隔离性和⼀致性,无法保证原子性与持久性。

持久性之前说了,我们还可以通过使用Redis持久化操作将数据丢失的时间窗口限制在1秒之内。

而对于原子性来说:在Redis中单条命令是原子性,这是由 redis 单线程保障的,但是多条命令能否用 multi + exec 来保证其原子性呢?

原子性探究

Redis 中 multi + exec 并不支持回滚,例如有初始数据如下

set a 1000
set b 1000
set c a

执行

multi
decr a
incr b
incr c
exec

执行 incr c 时,由于字符串不支持自增导致此条命令失败,但之前的两条命令并不会回滚

更为重要的是,multi + exec 中的读操作没有意义,因为读的结果并不能赋值给临时变量,用于后续的写操作,既然 multi + exec 中读没有意义,就无法保证读 + 写的原子性,例如有初始数据如下

set a 1000
set b 1000

假设 a 和 b 代表的是两个账户余额,现在获取旧值,执行转账 500 的操作:

get a /* 存入客户端临时变量 */
get b /* 存入客户端临时变量 */
/* 客户端计算出 a 和 b 更新后的值 */
multi
set a 500
set b 1500
exec

但如果在 get 与 multi 之间其它客户端修改了 a 或 b,会造成丢失更新

解决原子性问题

为了解决上面的问题,我们可以应用乐观锁或者lua脚本

乐观锁保证原子性

watch 命令,用来盯住 key(一到多个),如果这些 key 在事务期间:

  • 没有被别的客户端修改,则 exec 才会成功

  • 被别的客户端改了,则 exec 返回 nil

还是上一个例子

get a /* 存入客户端临时变量 */
get b /* 存入客户端临时变量 */
/* 客户端计算出 a 和 b 更新后的值 */
watch a b /* 盯住 a 和 b */
multi
set a 500
set b 1500
exec

此时,如果其他客户端修改了 a 和 b 的值,那么 exec 就会返回 nil,并不会执行两条 set 命令,此时客户端可以进行重试

lua 脚本保证原子性

Redis 支持 lua 脚本,能保证 lua 脚本执行的原子性,可以取代 multi + exec

例如要解决上面的问题,可以执行如下命令

eval "local a = tonumber(redis.call('GET',KEYS[1]));local b = tonumber(redis.call('GET',KEYS[2]));local c = tonumber(ARGV[1]); if(a >= c) then redis.call('SET', KEYS[1], a-c); redis.call('SET', KEYS[2], b+c); return 1;else return 0; end" 2 a b 500
  • eval 用来执行 lua 脚本
  • 2 表示后面用空格分隔的参数中,前两个是 key,剩下的是普通参数
  • 脚本中可以用 keys[n] 来引用第 n 个 key,用 argv[n] 来引用第 n 个普通参数

其中双引号内部的即为 lua 脚本,格式化如下

local a = tonumber(redis.call('GET',KEYS[1]));
local b = tonumber(redis.call('GET',KEYS[2]));
local c = tonumber(ARGV[1]); 
if(a >= c) then 
    redis.call('SET', KEYS[1], a-c); 
    redis.call('SET', KEYS[2], b+c); 
    return 1;
else 
    return 0; 
end

以上是关于Lua 解决 Redis 缓存原子性问题的主要内容,如果未能解决你的问题,请参考以下文章

一网打尽Redis Lua脚本并发原子组合操作

Redis_07_Lua脚本实现多条Redis命令原子性

Redis_05_Lua脚本实现多条Redis命令原子性

redis的lua脚本快速入门

Redis实战之使用Lua脚本保证原子性

redis原子性读写操作