redis 的Lua脚本调试

注意:


💡 分片集群模式下,lua脚本中务必保证key都在一个槽位,如果有多个key,但不在一个槽位,可以使用{}的hash tag强制指定key的槽位hash值,如果key涉及多个槽位,执行lua将会报错

在redis中,lua脚本中不能定义全局变量,也就是脚本被认为function,声明变量必须带上local;如果redis重启,之前缓存的lua 脚本将会丢失,这时需要重新load

不是万能的,尽量保证lua脚本短小精简,否则可能会影响redis的整体性能

搭建IDEA调试环境

调试SDK下载

github上有挺多类库的,下面是我用到的。

下载的windows的,下载解压后

redis lua配置 redis lua调试_redis lua配置


解压之后就可以了。

idea插件

EmmyLua

  1. 插件市场安装

redis lua配置 redis lua调试_redis lua配置_02

创建lua项目

  1. 创建lua项目

redis lua配置 redis lua调试_开发语言_03

  1. 配置lua项目的SDK路径

redis lua配置 redis lua调试_redis_04

  1. lua项目下面新建一个lua调试文件
  2. 写一个hello world 程序

redis lua配置 redis lua调试_java_05

  1. 打上断点,右键debug运行
    报错

配置lua启动路径

redis lua配置 redis lua调试_redis_06

到这里lua调试环境就搭建好了

连接redis

上面的lua的sdk里面没有redis的连接的类库,需要自己找

拷贝redis.lua脚本到项目下面

编写脚本,连接redis

redis lua配置 redis lua调试_redis_07

这里就配置好了,通过idea调试redis lua脚本的环境了。

💡 在线IDE工具

lua的基本语法

lua基本的语法,可以看下面教程

redis如何使用lua脚本

从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,支持lua脚本执行。

redis内置的库

  • base
  • table
  • string
  • math
  • debug
  • cjson
  • cmsgpack

redis中使用lua脚本步骤

  1. SCRIPT LOAD 命令加载脚本到缓存中
  2. 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脚本作为热更新的工具。