深入学习Redis_(一)五种基本数据类型、RedisTemplate、RedisCache、缓存雪崩等
深入学习Redis_(二)淘汰策略、持久化机制、主从复制、哨兵模式等
深入学习Redis_(三)事务、分布式锁、消息队列、延时队列等
Lua
是一种轻量小巧的脚本语言,用标准C语言
编写并以源代码形式开放,几乎在所有操作系统和平台上都可以编译,运行。 其设计目 的是为了嵌入应用程序中
,从而为应用程序提供灵活的扩展和定制功能。
官网:http://www.lua.org/
yum install ‐y gcc
yum install libtermcap‐devel ncurses‐devel libevent‐devel readline‐devel curl ‐R ‐O http://www.lua.org/ftp/lua‐5.3.5.tar.gz
tar ‐zxf lua‐5.3.5.tar.gz
cd lua‐5.3.5
make linux test
make install
LUA的基本语法 lua有交互式编程和脚本式编程。
交互式编程就是直接输入语法,就能执行。
脚本式编程需要编写脚本文件,然后再执行。
一般采用脚本式编程。(例如:编写一个hello.lua的文件,输入文件内容,并执行lua hello.lua即可)
创建helloworld.lua
文件,内容为
print("hello");
保存。执行命令
lua helloworld.lua
--单行注释
多行注释:
--[[
多行注释
多行注释
--]]
and、elseif、function、nil、return、while、break do、end false、if in、not or、then true、else、for、local、repeat、until
全局变量,默认的情况下,定义一个变量都是全局变量, 如果要用局部变量 需要声明为local.例如:
‐‐ 全局变量赋值
a=1
‐‐ 局部变量赋值
local b=2
如果变量没有初始化:则 它的值为nil
这和java中的null不同。
Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。
值可以存储在变量中,作 为参数传递或结果返回。
Lua 中有 8 个基本类型分别为:nil
、boolean
、number
、string
、userdata
、 function
、thread
和 table
。
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays), 数组的索引可以是数字、字符串或表类型。在 Lua 里,table的创建是 通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
如下:类似于if else
if 条件表达式 then
语句块
elseif 条件表达式 then
语句块
else
语句块
end
if(0) then
print("0 为 true")
else
print("0 不为true")
end
lua中也可以定义函数,类似于java中的方法。
--[[函数返回两个值的最大值--]]
function max(num1,num2)
if(num1 > num2) then
result = num1
else
result = num2
end
return result
end
--调用函数
print("两值比较最大值为 ",max(10,4))
print("两值比较最大值为 ",max(5,6))
require 用于 引入其他的模块,类似于java中的类要引用别的类的效果。
用法:
require "<模块名>"
例如:在demo.lua
脚本中引入module.lua
脚本
module.lua脚本
-- 定义一个模块
module = {
}
module.username = "张三"
--定义一个全局方法
function module.fun1()
print("fun1")
end
local function fun2()
print("fun2")
end
function module.fun3()
fun2()
end
return module
demo.lua脚本
-- 引入module
require("module")
print(module.username)
module.fun1()
module.fun3()
Redis执行Lua脚本是原子性
的,脚本执行期间Redis不会执行其他命令。所有命令都必须等待脚本执行完成后才能执行。
为了防止某个脚本执行时间过长导致Redis无法提供服务(比如陷入死循环),Redis提供了lua-time-limit
参数限制脚本的最长运行时间,默认为五秒钟。
当脚本运行时间超过这一限制后,Redis将开始接受其他命令但不会执行(保证脚本的原子性),而是会返回“BUSY
”错误。
此时虽然Redis可以接受任何命令,但实际会执行的只有两个:SCRIPT KILL
和SHUTDOWN NOSAVE
.
如果当前的脚本对Redis的数据进行了修改,则SCRIPT KILL
命令不会终止脚本的运行以防止脚本只执行了一部分,会违背脚本的原子性要求。
这时候只能通过SHUTDOWN NOSAVE
强行终止Redis。
EVAL script numkeys key [key ...] arg [arg ...]
官方文档:
Introduction to EVAL
EVAL and EVALSHA are used to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0.
The first argument of EVAL is a Lua 5.1 script. The script does not need to define a Lua function (and should not). It is just a Lua program that will run in the context of the Redis server.
The second argument of EVAL is the number of arguments that follows the script (starting from the third argument) that represent Redis key names. The arguments can be accessed by Lua using the KEYS global variable in the form of a one-based array (so KEYS[1], KEYS[2], …).
All the additional arguments should not represent key names and can be accessed by Lua using the ARGV global variable, very similarly to what happens with keys (so ARGV[1], ARGV[2], …).
The following example should clarify what stated above:
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
EVAL的第一个参数是一个Lua 脚本
。
EVAL的第二个参数是表示Redis键名的参数个数
。Lua
可以使用KEYS全局变量
以基于一个数组的形式访问这些参数(KEYS[1]
, KEYS[2]
,…)。
所有的附加参数不应该表示键名,可以通过Lua
使用ARGV全局变量
访问,这与使用键的情况非常相似(ARGV[1]
, ARGV[2]
,…)。
例如:
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 k1 k2 v1 v2
"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
是lua脚本
KEYS[1]、KEYS[2]
相当于key
的占位符,代表键:k1、k2
ARGV[1]、ARGV[2]
相当于value
的占位符,代表参数 v1、v2
lua脚本中可以使用redis.call
函数调用Redis命令。
redis.call(‘set’,‘test’,‘1’)
redis.call(‘get’,‘test’)
返回值就是Redis命令的执行结果。
Redis还提供了redis.pcall()
函数,功能和上面相同,唯一区别是当命令执行出错时redis.pcall会记录错误并继续执行,而redis.call会直接返回错误,不会继续执行。
> eval "return redis.call('set','foo','bar')" 0
OK
上面的脚本将键foo设置为字符串。然而,它违背了EVAL命令的语义,因为脚本使用的所有键都应该通过keys数组传递,正确的应该是:
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 k3 v3
OK
虽然第一种写法不规范,但是也没有错。
构造脚本使用DefaultRedisScript
类的构造方法。
使用execute
这个方法来执行lua脚本。
常用的方法有以下几个:
public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
return this.scriptExecutor.execute(script, keys, args);
}
/**
* script:要执行lua脚本,封装成RedisScript对象
* argsSerializer:参数序列化器
* resultSerializer:结果序列化器
* keys:Redis的键集合
* args:脚本所需的参数
*/
public <T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer, List<K> keys, Object... args) {
return this.scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args);
}
@Test
public void testLua() {
//设置k1 v1 并设置过期时间 最后返回 值
String script = "redis.call('set',KEYS[1],ARGV[1]) return redis.call('get',KEYS[1])";
// 两个参数 分别代表 lua脚本 和 返回值的类型
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
/**
* List设置lua的KEYS
*/
List<String> keyList = new ArrayList();
keyList.add("k1");
/**
* redisCacheTemplate.execute(redisScript, keyList, argv1)
* redisScript 代表脚本
* keyList代表key
* argv1 代表第一个参数 ARGV[1]
* 如果有多个参数 往后加
*/
String argv1 = "v1";
String execute = redisCacheTemplate.execute(redisScript, keyList, argv1);
System.out.println(execute);
}
// 使用指定参数和返回结果序列化方式的execute方法
//获取到字符串序列化器
RedisSerializer<String> stringSerializer = redisCacheTemplate.getStringSerializer();
String execute1 = redisCacheTemplate.execute(redisScript, stringSerializer, stringSerializer, keyList, argv1);
System.out.println(execute1);
在resources目录下创建setExpireAndGet.lua
文件
redis.call('set',KEYS[1],ARGV[1])
redis.call('expire',KEYS[1],10)
return redis.call('get',KEYS[1])
@Test
public void testLua2() {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(String.class);
redisScript.setLocation(new ClassPathResource("setExpireAndGet.lua"));
/**
* List设置lua的KEYS
*/
List<String> keyList = new ArrayList();
keyList.add("k2");
String argv1 = "v2";
String execute = redisCacheTemplate.execute(redisScript, keyList, argv1);
System.out.println(execute);
}
if redis.call('set',KEYS[1],ARGV[1],'NX','EX',ARGV[2]) then
return '1'
else
return '0'
end
参数含义:
只有当值匹配是才能执行删除锁的操作,解铃还须系铃人。
if redis.call('get',KEYS[1]) == ARGV[1] then
return tostring(redis.call('del',KEYS[1]))
else
return '0'
end