【Redis笔记】lua脚本
介绍
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Redis通过内置的 Lua 解释器,可以使用 EVAL
命令对 Lua 脚本进行求值。在lua脚本中可以通过两个不同的函数调用redis命令,分别是:redis.call()
和 redis.pcall()
脚本的原子性
Redis 使用 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC
包围的事务很类似。
这也意味着,执行一个运行缓慢的脚本并不是一个好主意,其他客户端会因为服务器正忙而无法执行命令。
Lua语法
启动
在linux任意目录中创建并运行hello.lua
echo 'print("hello world")' > hello.lua
lua hello.lua
数据类型
数据类型 | 描述 |
---|---|
nil | 只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
语法
定义变量:
-- 声明字符串
local str = 'hello'
-- 字符串拼接可以使用 ..
local str2 = 'hello' .. 'world'
-- 声明数字
local num = 21
-- 声明布尔类型
local flag = true
-- 声明数组 key为索引的 table
local arr = {'java', 'python', 'lua'}
-- 声明table,类似java的map
local map = {name='Jack', age=21}
访问table:
-- 访问数组,lua数组的角标从1开始
print(arr[1])
-- 访问table
print(map['name'])
print(map.name)
循环:
数组、table都可以利用for循环来遍历
遍历数组:
-- 声明数组 key为索引的 table
local arr = {'java', 'python', 'lua'}
-- 遍历数组
for index,value in ipairs(arr) do
print(index, value)
end
遍历table:
-- 声明map,也就是table
local map = {name='Jack', age=21}
-- 遍历table
for key,value in pairs(map) do
print(key, value)
end
函数
定义函数的语法:
function 函数名( argument1, argument2..., argumentn)
-- 函数体
return 返回值
end
例如,定义一个函数,用来打印数组:
function printArr(arr)
for index, value in ipairs(arr) do
print(value)
end
end
条件控制
if、else语法:
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
逻辑操作符:and 、 or 、not(如not(a and b))
Redis调用lua
eval
redis中eval
调用lua的语法格式:
eval lua-script key-num keys[],arg[]
1.lua-script:这个是我们lua写的脚本命令
2.key-num:这个是我们后面keys参数的个数
3.keys[]:参数名数组,当key-num为0时,这个可以省略
4.arg[]:与keys[]对应的参数值数组,当key-num为0时,这个可以省略
案例:
1、参数个数为0
eval "return 'hello world'" 0 #输出hello world
2、传参
eval "return KEYS[1] .. ARGV[1]" 1 a 11 #输出a11
调用redis命令
通过redis.call
调用redis命令,语法:
redis.call(command, key [param1, param2…])
执行set
命令:
eval 'redis.call("set",KEYS[1],ARGV[1])' 1 bb 22 #设置缓存`set bb 22`
get bb #输出22
lua脚本文件
将lua脚本放到文件中去,然后redis去调用:redis-cli –eval [lua 脚本] [key…]空格,空格[args…]
案例1、无参数
touch test.lua
文件内容:
redis.call('set','a','1')
return redis.call('get','a')
执行脚本文件:
redis-cli -a 123456 --eval test.lua #输出1
-a是密码
案例2、ip频率限制
IP 限流,对某个IP 频率进行限制 ,如:6 秒钟访问 10 次
创建ip-limit.lua脚本
local num=redis.call('incr',KEYS[1])
if tonumber(num)==1 then
redis.call('expire',KEYS[1],ARGV[1])
return 1
elseif tonumber(num)>tonumber(ARGV[2]) then
return 0
else
return 1
end
其中KEYS[1]是ip
ARGV[1]:为设置ip的有效时间
ARGV[2]:为设置限制的访问次数
执行命令:
比如 6秒钟之内只能访问2次,很显然,前面两次成功了,后面就不成功了
redis-cli --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 1
redis-cli --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 1
redis-cli --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 0
redis-cli --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 0
Redis脚本命令
- EVAL(执行lua脚本)
EVAL script numkeys key [key ...] arg [arg ...]
EVAL "return redis.call('set','KEYS[1]',ARGV[1])" 1 k1 v1
OK
127.0.0.1:6379> EVAL "return redis.call('get','KEYS[1]')" 1 k1
v1
- SCRIPT LOAD(lua脚本服务器缓存)
缓存lua脚本到redis服务器,在执行redis的命令的时候,因为lua脚本是命令或者文件的形式,每次都需要将lua脚本命令传递给redis服务端, 每一次调用,会产生比较大的网络开销;所以推荐将lua脚本缓存到redis服务端
SCRIPT load "return 'hello world'" --返回"5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
- SCRIPT EXISTS(查看脚本是否已经被保存在缓存当中)
SCRIPT EXISTS "5332031c6b470dc5a0dd9b4bf2030dea6d65de91" --返回1
- EVALSHA(执行脚本缓存)
EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0 #返回hello world
- SCRIPT FLUSH(清除所有脚本缓存)
SCRIPT FLUSH
- SCRIPT KILL(杀死正在运行的脚本)
这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限循环的脚本。
SCRIPT KILL
案例2、将上面ip限制脚本缓存到redis服务器
SCRIPT LOAD "local num=redis.call('incr',KEYS[1]);if tonumber(num)==1 then redis.call('expire',KEYS[1],ARGV[1]) return 1 elseif tonumber(num)>tonumber(ARGV[2]) then return 0 else return 1 end"
"766696820dc636809147aba60dcab8c488860ff5"
执行:
EVALSHA 766696820dc636809147aba60dcab8c488860ff5 1 127.0.0.1 6 2