相信大部分redis使用者,至少听过Lua脚本,使用的话,更好了,今天我以个人学习经验交流把我对lua的使用写出来。
首先看一下网上对lua脚本的介绍和使用:
以上是在菜鸟教程上找到的内容并截图。
教程里并没有具体的写出使用,至少很简单的描述了一个实例。
比如:
怎么设置key?
怎么设置分布式锁?
怎么删除key?
…
等等
这些,在菜鸟教程里面都没具体描述。所以我写该篇文章,主要是描述在实际的生产中的使用。
lua的简单介绍还是要写一下:
EVAL script numkeys key [key …] arg [arg …]
首先大家一定要知道eval的语法格式,其中:
<1> script: 你的lua脚本
<2> numkeys: key的个数
<3> key: redis中各种数据结构的替代符号
<4> arg: 你的自定义参数
例子:
eval “return {KEYS[1],KEYS[2],KEYS[3],KEYS[4]}” 4 username age jack 20
类似于C#的占位符{0}
通常情况下,不会在redis-cli直接写lua脚本,一般都是放在lua文件中,方便编辑
redis提供了以下几个script命令,用于对于脚本子系统进行控制:
script flush:清除所有的脚本缓存
script load:将脚本装入脚本缓存,不立即运行并返回其校验和
script exists:根据指定脚本校验和,检查脚本是否存在于缓存
script kill:杀死当前正在运行的脚本(防止脚本运行缓存,占用内存)
话不多说,直接上代码(golang版本的代码)
func demo_01() {
redis_client := GetRedisConnect()
/*result:=redis_client.ScriptLoad("return 'Hello GrassInWind'")
fmt.Println(result.Val())*/
// c66be1d9b54b3182f8d8e12f8b01a4e5c7c4af5b
result:=redis_client.EvalSha("c66be1d9b54b3182f8d8e12f8b01a4e5c7c4af5b",nil)
fmt.Println(result.Val())
}
func demo_02() {
redis_client := GetRedisConnect()
/*result:=redis_client.ScriptLoad("return {KEYS[1],KEYS[2],KEYS[3],KEYS[4]}")
fmt.Println(result.Val())*/
//00bd8c0c171fada34c1c511fadf893e57bdb429e
//4 username age jack 20
result:=redis_client.EvalSha("00bd8c0c171fada34c1c511fadf893e57bdb429e",[]string{"username","jack","age","20"})
fmt.Println(result.Val())
}
func demo_03() {
redis_client := GetRedisConnect()
//设置分布式锁
/* result:=redis_client.ScriptLoad("return redis.call('SETNX',KEYS[1],KEYS[2])")
fmt.Println(result.Val())*/
// ff1198b25bcb146e61488a7be91cc6df13ca40ce
result:=redis_client.EvalSha("ff1198b25bcb146e61488a7be91cc6df13ca40ce",[]string{"OrderId202102231123","11000"})
fmt.Println(result.Val())
}
//设置分布式锁+过期时间
func demo_04() {
redis_client := GetRedisConnect()
//设置分布式锁
/*result := redis_client.ScriptLoad("return redis.call('SET',KEYS[1],KEYS[2],'ex',KEYS[3],'nx')")
fmt.Println(result.Val())*/
//9a391811e7f132f4e3b57e173e126a43f8e63700
result := redis_client.EvalSha("9a391811e7f132f4e3b57e173e126a43f8e63700", []string{"OrderId202102231157", "22000","10000"})
fmt.Println(result.Val())
}
关于里面的使用,再简单介绍下:
func demo_01() {
//获取redis客户端连接
redis_client := GetRedisConnect()
//把lua脚本运行在redis服务端
/*result:=redis_client.ScriptLoad("return 'Hello GrassInWind'")
fmt.Println(result.Val())*/
//这是把lua脚本运行在redis服务端成功后返回的sha随机字符串编码
// c66be1d9b54b3182f8d8e12f8b01a4e5c7c4af5b
//此条lua脚本没有任务参数,执行的时候,只需要执行获取到的随机字符串编码即可得到结果
result:=redis_client.EvalSha("c66be1d9b54b3182f8d8e12f8b01a4e5c7c4af5b",nil)
fmt.Println(result.Val())
}
//设置分布式锁
func demo_04() {
redis_client := GetRedisConnect()
//设置分布式锁的lua脚本,
/*result := redis_client.ScriptLoad("return redis.call('SET',KEYS[1],KEYS[2],'ex',KEYS[3],'nx')")
fmt.Println(result.Val())*/
//9a391811e7f132f4e3b57e173e126a43f8e63700
result := redis_client.EvalSha("9a391811e7f132f4e3b57e173e126a43f8e63700", []string{"OrderId202102231157", "22000","10000"})
fmt.Println(result.Val())
}
//批量设置分布式锁
func demo_05() {
redis_client := GetRedisConnect()
script := `local str=KEYS[1]
local delimiter=","
local result=""
local dLen = string.len(delimiter)
local newDeli = ''
for i=1,dLen,1 do
newDeli = newDeli .. "["..string.sub(delimiter,i,i).."]"
end
local locaStart,locaEnd = string.find(str,newDeli)
local arr = {}
local n = 1
while locaStart ~= nil
do
if locaStart>0 then
arr[n] = string.sub(str,1,locaStart-1)
n = n + 1
end
str = string.sub(str,locaEnd+1,string.len(str))
locaStart,locaEnd = string.find(str,newDeli)
end
if str ~= nil then
arr[n] = str
end
for k,v in pairs(arr) do
local new_str=v
delimiter="-"
str=v
local dLen = string.len(delimiter)
local newDeli = ''
for i=1,dLen,1 do
newDeli = newDeli .. "["..string.sub(delimiter,i,i).."]"
end
local locaStart,locaEnd = string.find(str,newDeli)
local arr = {}
local n = 1
while locaStart ~= nil
do
if locaStart>0 then
arr[n] = string.sub(str,1,locaStart-1)
n = n + 1
end
str = string.sub(str,locaEnd+1,string.len(str))
locaStart,locaEnd = string.find(str,newDeli)
end
if str ~= nil then
arr[n] = str
end
local set_key=""
local set_value=""
for k, v in pairs(arr) do
if k==1 then
print("这是键",k,v)
set_key=v
else
print("这是值",k,v)
set_value=v
print("set_key=",set_key,"set_value=",set_value)
result=redis.call('SET',set_key,set_value,'ex','10000','nx')
end
end
end
return result`
result := redis_client.ScriptLoad(script)
fmt.Println(result.Val())
result1 := redis_client.EvalSha(result.Val(), []string{
"php-2020,js-2010"})
fmt.Println(result1.Val())
}
相信这个批量设置锁的功能,使用的比较多,但是大部分都是基于 redis命令实现的。并没有基于redis下的lua脚本实现的。本篇文章应该算是全网唯一有新颖的文章了吧!为了写这篇文章,花费了好几天的时间,因为在网上实在找不到现有的示例了。
//todo for循环计算值
func demo_06() {
redis_client := GetRedisConnect()
script:=" local Sum=0; for i=1,10 do Sum=i+Sum end;return Sum "
result :=redis_client.ScriptLoad(script)
fmt.Println(result.Val())
// 796b7c05ac15db5b8c11dc20a0e33e71dfcd973c
result1 := redis_client.EvalSha(result.Val(),nil)
fmt.Println(result1.Val())
}
//redis下lua函数
func demo_07() {
redis_client := GetRedisConnect()
script:=`function add(a)
print(a)
return a
end`
result :=redis_client.ScriptLoad(script)
fmt.Println(result.Val())
result1 := redis_client.EvalSha(result.Val(),[]string{"1"})
fmt.Println(result1.Val())
}
redis下的lua脚本是不支持函数的,但是把此代码拿去lua代码测试是能运行的,下面将详细讲一下为什么redis下的lua不支持函数。
//字符串拆分
func demo_08() {
redis_client := GetRedisConnect()
/*script:=`local str=KEYS[1]
local delimiter=","
local dLen = string.len(delimiter)
local newDeli = ''
for i=1,dLen,1 do
newDeli = newDeli .. "["..string.sub(delimiter,i,i).."]"
end
local locaStart,locaEnd = string.find(str,newDeli)
local arr = {}
local n = 1
while locaStart ~= nil
do
if locaStart>0 then
arr[n] = string.sub(str,1,locaStart-1)
n = n + 1
end
str = string.sub(str,locaEnd+1,string.len(str))
locaStart,locaEnd = string.find(str,newDeli)
end
if str ~= nil then
arr[n] = str
end
return arr`*/
script:=`local str=KEYS[1]
local split=function(str,delimiter)
local dLen = string.len(delimiter)
local newDeli = ''
for i=1,dLen,1 do
newDeli = newDeli .. "["..string.sub(delimiter,i,i).."]"
end
local locaStart,locaEnd = string.find(str,newDeli)
local arr = {}
local n = 1
while locaStart ~= nil
do
if locaStart>0 then
arr[n] = string.sub(str,1,locaStart-1)
n = n + 1
end
str = string.sub(str,locaEnd+1,string.len(str))
locaStart,locaEnd = string.find(str,newDeli)
end
if str ~= nil then
arr[n] = str
end
return arr
end `
result:=redis_client.ScriptLoad(script)
result1:=redis_client.EvalSha(result.Val(),[]string{"php-2020,js-1010,go-2021"})
fmt.Println(result1)
}
字符串拆分,一般都是配合redis命令使用,单独使用的不多。
在Redis中,运行脚本的命令有两个,分别为EVAL和EVALSHA,其中EVALSH是由EVAL衍生而来的。
EVAL
示例:
EVAL "return 'hello world'" 0
EVAL 是执行整个脚本代码体。
EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0 // 上一个脚本的校验和
而 EVALSHA 则要求输入某个脚本的 SHA1 校验和, 这个校验和所对应的脚本必须至少被 EVAL 执行过一次。或者曾经使用 SCRIPT LOAD 载入过这个脚本,因为 EVALSHA 是基于 EVAL 构建的。
1.为输入的脚本定义一个lua函数
2.执行这个lua函数
举个例子, 当执行命令 EVAL “return ‘hello world’” 0 时, Lua 会为脚本 “return ‘hello world’” 创建以下函数:
function f_5332031c6b470dc5a0dd9b4bf2030dea6d65de91()
return 'hello world'
end
其中, 函数名以 f_ 为前缀, 后跟脚本的 SHA1 校验和(一个 40 个字符长的字符串)拼接而成。 而函数体(body)则是用户输入的脚本。
以函数为单位保存 Lua 脚本有以下好处:
用简单的话说一下,通过ScriptLoad加载的lua脚本,会把lua的脚本加入到一个函数中,函数的名称是 字符串(SHA1 校验和(一个 40 个字符长的字符串)拼接而成的)。当在redis中,想再次执行这个函数,那通过EVALSHA 命令去执行。那也就是说,redis里的lua是不支持自定义函数的,只支持脚本命令,内置帮你定义函数,及时是在脚本里面定义闭包函数也是不支持的。