讲一讲lua脚本的原理

523 阅读3分钟

lua脚本

lua脚本为了保证多条命令组合的原子性。

事务

将一组需要一起执行的命令放在multi和exec两个命令之间,multi命令表示事务开始,exec命令表示事务结束,它们之间的命令原子顺序执行。

有些应用场景需要确保key没有被其他客户端修改过才执行事务,否则不执行,redis使用watch命令来实现该功能,watch命令是一个乐观锁。

如果出现语法错误,会造成整个事务无法执行,已经执行的命令不生效。

如果运行时命令错误,redis并不支持回滚

lua代码编写

# --- strings(字符串)
# local: 局部变量
local strings val = "world"
print(val) # world# ---- tables(数组)
# 数组下标从1开始计算
local tables myArray = {"redis","jedis",true,88.0} 
print(myArray[3])# truelocal int sum=0
# for遍历计算1-100的和
for i=1,100
do
  sum=sum+1
end
print(sum) # 5050
# while循环计算1-100的和
local int i=0
while i<=100
do
  sum=sum+1
  i=i+1
end
print(sum) # 5050# for遍历myArray,判断数组中是否包含"jedis"
for i=1,#myArray # 在数组变量前加#号,获知数组长度
do
  if myArray[i]=="jedis"
  then 
      print("true")
      break
  else
      --do nothing
end
​
# for遍历myArray索引下标和值
for index,value in ipairs(myArray) # ipairs函数
do
  print(index)
  print(value)
end
​
# 使用数组实现哈希
local tables user_1 = {age=28,name="tome"}
# str1..str2 是将两个字符串进行拼接
print("user_1 age is" .. user_1["age"])
# 使用内置函数pairs遍历user_1
for key,value in pairs(user_1)
do print(key .. value)
end    
  
# ---function函数 以function开头,以end结尾
# 实现contact函数拼接两个字符串
function contact(str1,str2)
    return str1..str2
end    
​
# redis.call函数可以直接在lua脚本中执行redis命令
> EVAL "return redis.call('PING')" 0
PONG
“127.0.0.1:6379> eval 'return redis.call("get", KEYS[1])' 1 hello
"world”

lua原理

lua环境

  1. 为了在redis服务器中执行lua脚本,redis服务器中内置了一个lua环境。
  2. 在lua环境中载入多个函数库
  3. 创建全局表格redis,表格中包含了对redis进行操作的函数,比如在lua脚本中执行redis命令的redis.call
  4. 使用redis自制的随机函数替换lua原有的带有副作用的随机函数,多次调用随机函数效果相同
  5. 创建排序复制函数,可以对redis命令结果进行排序,从而消除命令的不确定性
  6. 创建redis.pcall的错误报告辅助函数
  7. 将lua环境中的全局环境进行保护,防止执行lua脚本过程中将额外的全局变量添加到lua环境

lua环境协作组件

  1. 伪客户端,执行redis命令必须有相应的客户端状态,redis服务器专门为lua环境创建一个伪客户端,由伪客户端处理lua脚本中包含的redis命令
  2. lua_scripts字典。key是lua脚本的SHA1校验和,value是对应的lua脚本。被eval命令执行过的和被script load命令载入过的lua脚本保存在lua_scripts字典中。字典可以实现script exists和脚本复制功能。

eval命令可以直接对输入的脚本进行求值,evalSHA命令可以根据脚本的SHA1校验和对脚本求值。

eval

eval [脚本内容] [key个数] [key列表] [参数列表]
​
​
​
127.0.0.1:6379> eval 'return "hello " ..KEYS[1] .. ARGV[1]' 1 redis world
"hello redis world"
​
# 脚本较长,可以使用redis-cli --eval直接执行xxx.lua脚本
> redis-cli --eval xxx.lua

如果lua脚本较长,可以使用redis-cli --eval直接执行文件

evalsha

将lua脚本加载到redis服务器,得到脚本的SHA1校验和。使用SHA1作为参数可以直接执行对应lua脚本,避免每次发送lua脚本的开销。

> redis-cli script load "$(cat lua_get.lua)"
"7413dc2440db1fea7c0a0bde841fa68eefaf149c" # SHA1码
> evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
"hello redisworld"

其他脚本命令

  • SCRIPT EXISTS:根据输入的SHA1校验和,检查校验和对应的脚本是否存在于服务器
  • SCRIPT FLUSH:清除服务器中所有与Lua脚本有关的信息
  • SCRIPT KILL: 指示服务器停止执行未进行写入操作的超时运行的脚本,并向脚本客户端发送错误回复
  • SCRIPT LOAD:在lua环境为脚本创建相对应的函数,将脚本保存到lua_scripts字典中

参考

《redis设计与实现》

《redis开发与运维》

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情