Lua在Redis中的使用方式
redis中内嵌了Lua脚本的解释器,并提供了执行Lua脚本的入口“eval”命令,
格式为 EVAL script numkeys key [key …] arg [arg…] .
其中eval 为命令, script为执行的命令脚本, numkeys 为脚本中共涉及到的key的数量,后续接收若干个key的输入和若干个arg的输入.
整个脚本中使用KEYS[index]
,和ARGS[index]
来获取实际的输入有点类似于SQL的占位符。另外一层原因由于Redis集群的固有模式导致EVAL在集群中涉及多个KEY的操作时要求所有的KEY都在同一个Hash Solt上,集群环境中调用EVAL Redis会对脚本先做一个的校验。
KEYS[1] KEYS[2],是要操作的键,可以指定多个,在lua脚本中通过KEYS[1], KEYS[2]获取。【特别注意】这些键要现在redis中存在,不然就获取不到对应的值。
ARGV[1] ARGV[2],参数在lua脚本中通过ARGV[1], ARGV[2]获取。
redis 执行Lua的保证
Redis中保证对一个Lua脚本执行的完整性,也就是说一个Lua脚本的执行只会有成功和失败,且保证在Redis Server端同时只会有一个Lua脚本在运行,这样就意味着Lua脚本中的操作是一个完整的原子操作,不会伴随中间状态和资源竞争,同时也意味着在Lua脚本中不适合进行一些耗时长的操作.由于有以上的保证,使用Redis来进行一些复杂的原子操作就在合适不过了,setNx方法的局限性也被Redis Lua进行了弥补.
Redis对嵌入的Lua做了若干的限制,包保证脚本不对Redis 造成破坏.不提供访问系统状态的库,禁止使用loadfile函数,禁止带有随机性质的命令或者带有副作用的命令, 对随机读命令的结果进行排序,替换math原有的random方法,不允许定义函数,不允许声明全局变量等等.
要注意的是Lua中 0 为 true。
在脚本中调用redis命令
在脚本中可以使用redis.call函数调用Redis命令
redis.call('set', 'foo', 'bar')
local value=redis.call('get', 'foo') --value的值为bar
redis.call函数的返回值就是Redis命令的执行结果
Redis命令的返回值有5种类型,redis.call函数会将这5种类型的回复转换成对应的Lua的数据类型。
Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
Redis分布式锁
分布锁满足两个条件,一个是加有效时间的锁,一个是高性能解锁
采用redis命令setnx(set if not exist)、setex(set expire value)实现
【千万记住】解锁流程不能遗漏,否则导致任务执行一次就永不过期
分布式锁setnx、setex的缺陷,在setnx和setex中间发生了服务down机
从Redis宕机讲解分布式锁执行的异常场景流程
从Server服务宕机讲解分布式锁执行的异常场景流程
在setnx和setex中间发生了服务down机 那么key将没有超时时间 会一直存在,新的请求永远进不来
解决方案:
由于setnx与setex是分步进行,那么我们将两步合成一步,放在同一个原子中即可
怎么一次性执行过一条命令而不会出现问题,采用Lua脚本
Redis从2.6之后支持setnx、setex连用
Lua简介
从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。
* Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。
在 Lua 脚本中,可以使用redis.call()来执行 Redis 命令
Lua脚本配置流程
1、在resource目录下面新增一个后缀名为.lua结尾的文件
2、编写lua脚本
local lockKey = KEYS[1]
local lockTime = KEYS[2]
local lockValue = KEYS[3]
-- setnx info
local result_1 = redis.call('SETNX', lockKey, lockValue)
if result_1 == 1
then
local result_2= redis.call('SETEX', lockKey,lockTime, lockValue)
return result_2
else
return 'faild'
end
3、传入lua脚本的key和arg
4、调用redisTemplate.execute方法执行脚本