redis 的Lua脚本调试
注意:
💡 分片集群模式下,lua脚本中务必保证key都在一个槽位,如果有多个key,但不在一个槽位,可以使用{}的hash tag强制指定key的槽位hash值,如果key涉及多个槽位,执行lua将会报错
在redis中,lua脚本中不能定义全局变量,也就是脚本被认为function,声明变量必须带上local;如果redis重启,之前缓存的lua 脚本将会丢失,这时需要重新load
不是万能的,尽量保证lua脚本短小精简,否则可能会影响redis的整体性能
搭建IDEA调试环境
调试SDK下载
github上有挺多类库的,下面是我用到的。
下载的windows的,下载解压后
解压之后就可以了。
idea插件
EmmyLua
- 插件市场安装
创建lua项目
- 创建lua项目
- 配置lua项目的SDK路径
- lua项目下面新建一个lua调试文件
- 写一个hello world 程序
- 打上断点,右键debug运行
报错
配置lua启动路径
到这里lua调试环境就搭建好了
连接redis
上面的lua的sdk里面没有redis的连接的类库,需要自己找
拷贝redis.lua脚本到项目下面
编写脚本,连接redis
这里就配置好了,通过idea调试redis lua脚本的环境了。
💡 在线IDE工具
lua的基本语法
lua基本的语法,可以看下面教程
redis如何使用lua脚本
从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,支持lua脚本执行。
redis内置的库
- base
- table
- string
- math
- debug
- cjson
- cmsgpack
redis中使用lua脚本步骤
-
SCRIPT LOAD
命令加载脚本到缓存中 -
EVALSHA
执行命令,参数就是第一步的hash值,redis就会执行第一步hash对应的脚本,并返回值
简单demo如下
@Test
public void testLua() throws IOException {
// 忽略 初始redis 代码
Jedis jedis = redisPool.getResource();
ResourceScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("lua/LuaLab.lua"));
String scriptAsString = scriptSource.getScriptAsString();
String hash = jedis.scriptLoad(scriptAsString);
Object eval = jedis.evalsha(hash);
System.out.println("eval = " + eval);
}
应用
redisson的分布式锁
redisson分布式锁就是通过lua脚本实现的
加锁:
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
final long threadId = Thread.currentThread().getId();
// 1、调用lua,加上锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
// lua中返回nil,说明加锁成功了
return true;
}
// 2、超过加锁等待时间,返回加锁失败
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// 3、以下省略几十行代码,其主要目的是不断尝试加锁,直到加锁超时
current = System.currentTimeMillis();
}
释放锁:
--- lock key不存在说明锁已经过期,直接返回即可,返回之前告诉订阅者
if (redis.call('exists', KEYS[1]) == 0)
then
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
--- 加锁线程已不存在,直接返回
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0)
then
return nil;
end;
--- 可重入设计,给加锁线程递减
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
--- 如果counter大于0,说明锁重入多次,那么再设置下过期时间
if (counter > 0)
then
redis.call('pexpire', KEYS[1], ARGV[2]);
return 0;
else
--- 没有重入多次,锁可以释放了
redis.call('del', KEYS[1]);
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
return nil;
库存
电商库存扣减,回滚等是一个比较复杂的场景。通过数据库可以实现但是无法承受高并发。
所以很多库存扣减就是通过lua脚本进行,保证原子性。
游戏
游戏业务是多变的,如何在多变的业务场景下,让用户不需要升级客户端直接修改逻辑。lua脚本就是一个好办法,lua脚本轻量,用C语言写,适配性高,所以很多游戏业务都用lua脚本作为热更新的工具。