Redis中使用lua脚本

时光毁灭记忆、已成空白 提交于 2020-01-29 21:43:30

1.问题

原子性问题
Redis虽然是单线程的,但是仍然可能会出现线程安全问题,当然这个线程安全问题不是来源于Redis服务器内部,而是多个客户端去操作Redis的时候,多个客户端的操作就相当于同一个进程下的多个线程,如果多个客户端之间没有做好同步策略,就会产生数据不一致的问题。比如:
我想按照顺序执行几个Redis命令,但是多个客户端的命令之间没有做请求同步,导致实际执行顺序与预想的不一致,最终的结果也就无法满足原子性。

执行效率问题
Redis由于是基于内存的数据库,它本身的吞吐量是非常高的,那么影响Redis吞吐量的一个重要因素是网络,我们用redis实现某些特定功能,很可能需要用多个命令、多个数据类型、多个步骤一起才能完成功能,那么在实现这个功能的过程中,就会产生多次对Redis的网络访问,多次向Redis发送和获取数据,这种多次网络请求对性能影响是非常大的。当然我们可以使用连接池、pipeline管道操作,但是它们有一定的局限性和应用场景,如果我们能将一系列的对redis的操作变成一次操作,那会大大地减少网络请求,提升效率;

Lua语言

Lua是一个高效的轻量级脚本语言 (javascript、shell、sql、perl、python),用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能;

Redis中内嵌了Lua运行环境,我们可以使用Lua语言编写脚本传到Redis中执行,执行完之后,redis会把结果返回给我们客户端,Redis客户端可以使用Lua 脚本,直接在服务端原子性地执行多个Redis命令。
使用脚本的好处:
1、原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入,换句话说,编写脚本的过程中无需担心会出现插队、竞争等条件;
2、减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行;

2.在Redis中使用Lua脚本

我们可以编写Lua脚本,然后再Lua脚本中调用Redis命令,使用redis.call函数调用。

比如调用string类型的命令:

eval "redis.call('set', 'k1', 'v1')" 0
eval "return redis.call('get','k1')" 0

eval "redis.call('set', KEYS[1], ARGV[1])" 1 K2 V2
eval "return redis.call('get', KEYS[1])" 1 K2

KEYS[1]表示key
ARGV[1]表示value

我们还可以写一个lua脚本文件,并把脚本文件保存起来,然后执行lua脚本文件:

abc.lua脚本文件代码:

redis.call('set', 'k1', 'v1')
return redis.call('get', 'k1')

执行lua脚本:
./redis-cli --eval /root/abc.lua -a 123456
其中–eval是执行,/root/abc.lua是脚本文件,-a 123456是指定redis密码
在脚本中可以使用return 语句将值返回给redis客户端,如果没有执行return, 默认返回为nil。
redis会自动将脚本返回值的Lua数据类型转化为Redis的返回值类型,然后返回给我们客户端。

EVAL命令的格式

[EVAL] [脚本内容] [key参数的数量] [key …] [arg …]
可以通过key和arg这两个参数向脚本中传递参数数据,这两个参数在脚本中分别使用KEYS和ARGV 这两个类型的全局变量接收,KEYS和ARGV 必须大写。
当脚本不需要任何参数时,需要传递一个0参数;

3.jedis使用

Jedis jedis = JedisPoolInstance.getJedisPoolInstance().getResource();
       // 设置值
        Object eval = jedis.eval("return redis.call('set','k2','v2') ",0);
        System.out.println(eval);
        // 取出值
        Object k2 = jedis.eval("return redis.call('get',KEYS[1])", 1, "k2");
        System.out.println(k2);

4.在Spring中调用

DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
        redisScript.setResultType(String.class);
        redisScript.setScriptText("return redis.call('get', 'k1')");

        //加载classpath下的一个lua脚本文件
        //redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limit.lua")));

        Object object = redisTemplate.execute(redisScript, null);
        System.out.println(object);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!