淘先锋技术网

首页 1 2 3 4 5 6 7

redis 的Lua脚本调试

注意:

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

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

搭建IDEA调试环境

调试SDK下载

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

下载地址:

http://luadist.org/

下载的windows的,下载解压后
在这里插入图片描述
解压之后就可以了。

idea插件

EmmyLua

  1. 插件市场安装

在这里插入图片描述

创建lua项目

  1. 创建lua项目

在这里插入图片描述

  1. 配置lua项目的SDK路径

在这里插入图片描述

  1. lua项目下面新建一个lua调试文件

    在这里插入图片描述

  2. 写一个hello world 程序

在这里插入图片描述

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

    报错

    在这里插入图片描述

    配置lua启动路径

在这里插入图片描述

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

连接redis

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

https://github.com/nrk/redis-lua/blob/master/src/redis.lua

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

编写脚本,连接redis

在这里插入图片描述

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

https://www.lua.org/demo.html

lua的基本语法

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);
    }

参考文档:http://doc.redisfans.com/script/eval.html#id3

应用

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脚本作为热更新的工具。