-
先开启redis的日志输出
修改redis.conf文件,设置logfile /root/tools/redis-6.0.9/logs/redis.log
-
重启redis
systemctl restart redisd -
创建一个简单的lua脚本test.lua
--在redis日志文件中输入日志,并且日志级别是redis.LOG_NOTICE redis.log(redis.LOG_NOTICE,"测试打印控制台") return "123123123"
-
执行脚本
../redis-cli --eval test.lua
返回值
日志文件的控制台输入
-
修改test.lua
--相当于 set key1 value1 redis.call("set","key1","value1") --相当于 get key1,然后把结果赋值给 var1 local var1 = redis.call("get","key1") --返回var1 return var1;
执行结果
查看redis中的 键多了一个名为key1的键
-
redis.call 和 redis.pcall的区别
-
redis.call 如果遇到单挑命令错误,会中断整个脚本的执行,已经执行的不会回退
--这句正常执行 redis.call("set","key1","value2") --call 执行set1 这种非法的命令出错以后,直接抛出异常 redis.call("set1","key1","value1") --相当于 get key1,然后把结果赋值给 var1 local var1 = redis.call("get","key1") --返回var1 return var1;
正常返回:
值已经被修改:
-
redis.pcall 如果遇到单条语句失败,会继续执行完整个脚本,已经执行的不会回退
--这句正常执行 redis.call("set","key1","value2") --pcall 执行set1 这种非法的命令出错以后,后面的命令依旧会执行(set1 是非法的命令) redis.pcall("set1","key1","value1") --相当于 get key1,然后把结果赋值给 var1 local var1 = redis.call("get","key1") --返回var1 return var1;
返回结果:
值已经被修改:
-
-
lua 脚本的注释
-
单行注释
--这里是注释
-
多行注释
--[[ 这里是多行注释 这里是注释的第二行 ]]
-
-
参数的传入
-
传入参数:redis-cli --eval 脚本名字 键1 键2 , 值1 值2 注意键和值中间有个逗号,键和值任意多个,并且可以不对称
../redis-cli --eval test.lua k1 k2 , v1 v2 -
上面命令的键和值没有对应关系,可以把他们看出两种类型的参数,或者是会放到两个数组里面的参数,如果我们需要传入类似map这种,可以用参数KEYS 和 ARGV下标的对应关系老实现。
-
使用参数,KEYS获取key的数组,ARGV获取值的数据,可以通过下标获取具体那个参数
redis.log(redis.LOG_NOTICE,cjson.encode(KEYS)) redis.log(redis.LOG_NOTICE,cjson.encode(ARGV)) redis.log(redis.LOG_NOTICE,cjson.encode(KEYS[1])) redis.log(redis.LOG_NOTICE,cjson.encode(ARGV[1]))
参数打印
-
-
redis内置的lua库函数
- redis.call 以会中断脚本执行的方式执行redis命令
- redis.pcall 以不中断脚本执行的方式执行redis命名
- cjson.encode 把lua的table装换成json字符串。
- cjson.decode 把json字符串转成 lua脚本的 table 对象。
- cmspack.pack 把table序列化成字符串
- cmspack.unpack 把字符串还原成表
-
参数类型对应
-
redis的数字-->lua的数字
-
redis 的字符串-->lua的字符串
-
redis的多行字符串-->lua的表
-
redis执行状态返回值-->lua的表(ok,或者err)
返回状态table是这个样子 {"ok":"OK"}
{"err":"@user_script: 4: Unknown Redis command called from Lua script"}
如果直接 return table,返回的是value部分。
-
-
脚本加入缓存,然后执行
-
需要先进入redis-cli才能执行,不能在linux的命令行执行,只能导入文本的脚本,不能是文件
-
导入脚本
script load "脚本内容"
-
检查脚本是否存在
script exists "30dc9ef2a8d563dced9a243ecd0cc449c2ea0144" -
执行脚本,需要指定 0 个参数
evalsha "30dc9ef2a8d563dced9a243ecd0cc449c2ea0144" 0
- 清空缓存的脚本
script flush
-
-
lua脚本长时间执行或者死循环问题
- 当执行时间大于lua-time-limit以后,redis 会接受io请求,这时候在命令行执行脚本会有redis繁忙的提示。
- lua-time-limit建议一定要设置,0或者负数表示没有限制。
- 如果这时候正在运行的lua脚本没有执行过修改操作,那么可以通过 script kill 杀掉正常执行的脚本。
- 如果已经有修改操作,redis为了保证原子性,是不让直接杀掉的。只能重启redis 服务,但是这样可能打破原子性。
-
redis 的lua 脚本的原子性建立在单线程按序执行的基础上,并且要求redis server不能在无故重启。或者说redis lua脚本只是保证了按序执行,不被插队,并没有保证原子性,配合单线程执行命令,并且在不会突然重启的基础上,才能保证原子性。
-
redis 的lua脚本中的操作没有隔离性。但是因为它是单线程执行命令的,别的线程进不来,没法读取到中间数据,所以表现的有隔离性。如果这时候被断电中断,就可以看到部分的中间数据。
-
redis 里面的 lua 是在沙箱中运行的,不能使用全局变量,不能调用操作系统和文件等资源,只能对redis内部数据做修改。redis也对随机做了处理,2次脚本执行,脚本里面的第N次获取的随机数总是一样的。需要要不一样,需要通过随机数种子来实现。
-
在java程序中使用lua脚本
配置按照string类型序列化redisTemplate/** * * redisTemplateString 用于处理字符串格式,或者不带类型的json字符串 * * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<String, String> redisTemplateString(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); //相当于key,value都使用string类型序列化 redisTemplate.setDefaultSerializer( new StringRedisSerializer() ); return redisTemplate; }
lua脚本
--相当于 set key1 value1 redis.call("set", KEYS[1] , ARGV[1] ); redis.call("set", KEYS[2] , ARGV[2] ); --相当于 get key1,然后把结果赋值给 var1 local value1 = redis.call("get",KEYS[1]); local value2 = redis.call("get",KEYS[2]); local data = {}; data.data1 = value1; data.data2 = value2; --返回json字符串 return cjson.encode( data ); --如果java那边用的带有类型的json需要在返回值两边加上字符串标记。 --return "\"" .. "asdasdasd" .. "\"";
使用java程序调用lua脚本
@Resource RedisTemplate<String, String> redisTemplateString; @ApiOperation(value = "调用lua脚本") @RequestMapping(value="lua/t1", method= {RequestMethod.GET}) public Object t2() throws Exception{ DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(String.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/test.lua"))); //RedisScript<Map> redisScript = RedisScript.of(new ClassPathResource("lua\\test.lua")); List<String> keys = new ArrayList<>(); keys.add("k1"); keys.add("k2"); Object rt = redisTemplateString.execute(redisScript, keys, "v1","v2"); System.out.println( rt.getClass() ); return rt; }
需要注意的是如果是带有java类型的json序列化方式和 不带类型的json序列化方式不兼容的。
如果是返回string类型并且加了"内容",是可以兼容的。
lua脚本返回值使用的是value类型的序列化方式。