当前位置: 首页 > article >正文

Redis中如何使用lua脚本-即redis与lua的相互调用

Redis Lua

Lua是一个高效的轻量级脚本语言。Lua 在葡萄牙语中是“月亮”的意思,它的徽标形似卫星,寓意着Lua是一个“卫星语言”,能够方便地嵌入到其他语言中使用。

在这里插入图片描述
在这里插入图片描述

使用脚本的好处

在这里插入图片描述

  • 减少网络开销:将多个命令合并到一条lua脚本中,发送给redis服务器,可以有效避免因为一次命令发送一次导致的网络开销。
  • 原子操作:Redis将整个脚本作为一个整体执行,保证原子性。
  • 复用:客户端编写的lua脚本会永久存储到redis中,这也就意味着其他客户端也可以复用这一脚本。

Redis Lua语法

数据类型

在这里插入图片描述

类型名取值
nil空类型只包含一个值,即nil,所有没有赋值的变量或表的字段都是nil
boolean布尔类型,取值true和false
number整数 和浮点数都使用数字类型存储
string字符串,包括单引号和双引号,字符串中也能包含转义字符
table表类型是lua语言中唯一的数据结构,即可以当数组也可以当字典,十分灵活
function函数,函数可以作为参数传递给其他函数,也可以作为返回值

变量

Lua变量分为局部变量和全局变量,局部变量只在当前函数中有效,全局变量可以在任何地方访问,但是全局变量在多个脚本中可能会冲突,所以不建议使用全局变量,而且全局变量无需声明,可以直接使用,默认值是nil。

a = 1 -- 为全局变量a赋值 
print(a) -- 无需声明,直接使用,默认是nil 
a = nil -- 删除全局变量a的方法是将其赋值为nil 
local b -- 声明一个局部变量b,默认值是nil 
local d = 1 -- 声明一个局部变量d,并赋值1 
local e, f -- 可以同时声明多个变量 
-- 声明一个局部函数 
local say_hello = function g() print "hello" end 
-- 变量名只能包含字母、数字、下划线,不能包含其他符号,且不能以数字开头,比变量名不能与lua的保留关键字相同

Lua的保留关键字

类别关键字说明
控制结构and逻辑与运算符
or逻辑或运算符
not逻辑非运算符
if条件判断开始
then条件判断块的开始
else条件判断的替代分支
elseif多个条件判断分支
while循环结构,当条件为真时重复执行
do开始一个代码块
end结束一个代码块
repeat重复循环,直到条件为真
until重复循环的条件判断
for循环结构,用于遍历或计数
in用于 for 循环中的范围定义
函数和方法function定义函数
return返回函数结果
local定义局部变量
表和对象table表类型关键字(虽然不是保留关键字,但在表操作中常用)
nil空值
true布尔值真
false布尔值假
元表和元方法__index访问表中不存在的键时调用的方法
__newindex设置表中不存在的键时调用的方法
__call当表被当作函数调用时调用的方法
__metatable获取或设置表的元表时调用的方法
__tostring将表转换为字符串时调用的方法
__len获取表的长度时调用的方法
__pairs遍历表时调用的方法
__ipairs遍历表索引时调用的方法
__concat连接两个表时调用的方法
__add加法运算时调用的方法
__sub减法运算时调用的方法
__mul乘法运算时调用的方法
__div除法运算时调用的方法
__mod取模运算时调用的方法
__pow幂运算时调用的方法
__unm取反运算时调用的方法
__eq等于比较时调用的方法
__lt小于比较时调用的方法
__le小于等于比较时调用的方法
其他goto跳转到标签
break终止循环或 switch 语句
do开始一个代码块
end结束一个代码块

注释

Lua支持单行注释和块注释,单行注释以--开头,块注释以--[[开始,以]]结束,两者之间可以包含任意多行。

赋值

Lua支持变量赋值,变量赋值语法为:变量名 = 表达式,变量名可以是一个或多个变量,表达式可以是一个或多个表达式,多个表达式之间用逗号隔开,多个变量之间用逗号隔开。

操作符

Lua有以下5种操作符:

  • 算术运算符+ - * / % ^ —— 操作符会自动转换数据类型,如数字和字符串相加时,字符串会被转换成数字再进行计算,例如:1 + "2" = 3
  • 比较运算符== ~= < > <= >=
  • 布尔运算符and or not
  • 连接运算符..
  • 取长度运算符# —— 例如:#'hello' = 5

各个操作符的优先级如下:

  1. ^ (指数)
  2. - (负号,一元运算符)
  3. * / % (乘、除、取余)
  4. + - (加、减)
  5. .. (字符串连接)
  6. < > <= >= ~= == (关系运算符,不等于和等于)
  7. and
  8. or

if语句

在Lua中只有nilfalse是假值,其余值包括空字符串、0、空表等都是真值。因此判断Redis返回值需要特别注意,否则就容易出错。

Lua和C语言一样每个语句都可以在末尾添加分号,但是一般编写Lua代码时可以省略分号。Lua也不强制要求使用缩进,但是建议使用缩进,这样代码更易读。

循环语句

Lua支持三种循环语句:forwhilerepeat

for循环
for 变量名 = 初始值, 结束值, 步长 do
    -- 循环体
end
-- 步长可以省略,默认为1
for i = 1, 10 do
    print(i)
end
    -- 循环体
end
while循环
while 条件 do
    -- 循环体
end
repeat循环
repeat
    -- 循环体
until 条件

表类型

表是Lua中唯一的数据结构,可以理解为关联数组,任何类型的值(除了空类型)都可以作为表的索引。

创建一个表
local a = {}
-- b 是一个和传统数组一样的表, b[1] = 1, b[2] = 2, b[3] = 3
local b = {1, 2, 3}
-- 可以使用for语句遍历数组,#b可以获取数组的长度
for i = 1, #b do
  print(b[i])
end
-- ipairs是lua提供的一个内置函数,实现迭代器功能,可以遍历数组,返回索引和值
for index, value in ipairs(b) do
  print(index, value)
end

-- c 是一个和传统数组一样的表,c[1] = "hello", c[2] = "world"
local c = {"hello", "world"}
-- d 是一个表,d.a = 1, d.b = 2, d.c = 3
local d = {
  a = 1,
  b = 2,
  c = 3
}
-- lua还提供一个迭代器,pairs,可以遍历非数组形式的表,返回键和值
for key, value in pairs(d) do
  print(key, value)
end

-- e 是一个表,当访问e.a时,返回"a not found"
local e = setmetatable({}, {
  __index = function(t, k)
    return k .. " not found"
  end
})
print(e.a)

f['field'] = 'value'  -- 将field字段的值设置为value
print(f.field)        -- 打印内容为value,a.field是a["field"]的语法糖

函数

lua中的函数可以有0个或多个参数,参数以列表的形式提供可以是任意类型。

定义一个函数

-- 定义一个函数
function func(arg1, arg2, ...)
  -- 函数体
  return value1, value2, ...
end
-- 将函数赋值给变量
local func = function(num)
  return num * num
end
-- 函数可以借助lua提供的语法糖简写为
local func
func = function(num)
    return num * num
end

标准库

lua提供了丰富的标准库,包括base、string、table、math、debug等,下面简单介绍几个常用的。

string

-- string.len(s)和#的作用类似,返回字符串s的长度
local str = "hello world"
print(string.len(str))
-- 上述代码等价于
print(#str)
-- 转换大小写
print(string.upper(str))
print(string.lower(str))
-- 字符串连接
print(string.format("%s %s", "hello", "world"))
print(string.rep("*", 10))
-- 字符串分割
local str = "hello,world"
local t = string.split(str, ",")
for i, v in ipairs(t) do
  print(i, v)
end
-- 获取子字符串
print(string.sub(str, 1, 5))

table

-- table库中大部分函数都需要是数组的形式
local t = {"hello", "world"}
-- 将数组转换成字符串,中间间用逗号分隔
print(table.concat(t, ","))
local t = {1, 2, 3}
-- 排序
table.sort(t)
for i, v in ipairs(t) do
  print(i, v)
end
-- 插入元素
table.insert(t, 4)
-- 弹出元素
table.remove(t)
-- 获取元素
print(t[1])

math

lua提供了丰富的数学函数,包括abs、ceil、floor、max、min、mod、random、randomseed、sqrt、type、ult等。

-- math.abs(x)返回x的绝对值
print(math.abs(-1))
-- math.ceil(x)返回大于等于x的最小整数
print(math.ceil(1.1))
-- math.floor(x)返回小于等于x的最大整数
print(math.floor(1.1))
-- math.max(x, y, ...)返回x, y, ...中的最大值
print(math.max(1, 2, 3))
-- math.min(x, y, ...)返回x, y, ...中的最小值
print(math.min(1, 2, 3))
-- math.mod(x, y)返回x除以y的余数
print(math.mod(5, 2))
-- math.random(x)返回[1, x]之间的随机数
print(math.random(10))
-- math.random()
print(math.random())
-- math.randomseed(x)设置随机数种子,如果不设置,每次生成的随机数都是一样的
math.randomseed(os.time())
print(math.random())
-- math.sqrt(x)返回x的平方根
print(math.sqrt(9))
-- math.ult(x, y)返回x是否小于y,x和y都是无符号整数,返回值是布尔值
print(math.ult(1, 2))

其他库

除了lua中的标准库,redis还提供了一些其他库,包括cjson、cmsgpack等,redis会自动加载这这些库,在脚本中可以通过cjson和cmsgpack两个全局变量进行访问。

cjson

cjson提供了json格式的序列化和反序列化功能,使用起来非常方便。

-- 加载 cjson 库
local cjson = require('cjson')

-- 示例 JSON 字符串
local json_str = '{"name": "Alice", "age": 30, "city": "New York"}'

-- 解析 JSON 字符串
local data = cjson.decode(json_str)

-- 修改数据
data.age = 31

-- 生成新的 JSON 字符串
local new_json_str = cjson.encode(data)

-- 将新的 JSON 字符串存储到 Redis 中
redis.call('SET', 'user:alice', new_json_str)

-- 返回新的 JSON 字符串
return new_json_str

redis与lua

编写lua脚本就是为了让redis能够调用,接下来我们看下redis如何与lua脚本交互的。

在脚本中调用redis
在lua脚本中,可以通过redis.call()函数调用redis,第一个参数是redis命令的名称,后面的命令的参数。

例如,我们可以在lua脚本中调用redis的set命令,设置一个键值对。

-- 在lua脚本中调用redis的set命令
redis.call("SET", "key", "value")
-- 在lua脚本中调用redis的get命令
redis.call("GET", "key")

redis命令执行有5种类型回复,redis.call()会将这五种类型回复转换成lua中的类型,具体如下:

redis返回值类型lua数据类型
integernumber
bulk stringstring
multi bulk stringtable - 数组形式
statustable (只有一个ok字段存储状态信息)
errortable (只有一个err字段存储错误信息)

在很多情况下都需要脚本可以返回值,比如前面的访问频率限制脚本会返回访问频率是否超限。在脚本中可以使用return语句将值返回给客户端,如果没有执行return语句则默认返回nil。因为我们可以像调用其他Redis内置命令一样调用我们自己写的脚本,所以同样Redis会自动将脚本返回值的Lua数据类型转换成Redis的返回值类型。

lua数据类型和redis返回值类型转换表如下:

lua数据类型redis返回值类型
numberinteger
stringbulk string
table - 数组形式multi bulk string
table (只有一个ok字段存储状态信息)status
table (只有一个err字段存储错误信息)error

脚本相关命令

编写完lua脚本后,最总要的就是执行,redis提供了EVAL命令,可以使开发者像调用其它redis内置命令一样调用脚本。

EVAL命令的参数如下:

-- 可以通过key和arg参数来向lua脚本传入参数,他们的值可以在脚本中通过KEYS和ARGV两个表类型的全局变量访问。
EVAL script numkeys key [key ...] arg [arg ...]

调用示例:

-- 设置键值对,等价于 SET key hello
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 key hello
-- 获取键值,等价于 GET key
EVAL "return redis.call('GET', KEYS[1])" 1 key
EVAL "local key = KEYS[1]; local value = redis.call('GET', key); if value then return value; else return 'Key does not exist'; end" 1 key

考虑到脚本比较长的情况下,如果每次调用都需要将整个脚本传递个redis会占用较多网络带宽,为了解决这个问题,redis提供了EVALSHA命令,可以先将脚本加载到redis中,然后再通过sha1值调用脚本。

redis在执行EVAL命令时会计算脚本的sha1值,并将值保存在redis中,下次执行EVALSHA命令时,会先根据sha1值查找redis中是否有对应的脚本,如果有则直接执行,否则会报错。
EVALSHA命令的参数如下:

-- 可以通过key和arg参数来向lua脚本传入参数,他们的值可以在脚本中通过KEYS和ARGV两个表类型的全局变量访问。
EVALSHA sha numkeys key [key ...] arg [arg ...]

调用示例:

  1. 使用 EVAL 命令加载 Lua 脚本:首次运行脚本时,使用 EVAL 命令将脚本加载到 Redis 服务器中,并获取脚本的 SHA1 哈希值。
  2. 使用 EVALSHA 命令执行脚本:后续调用时,使用 EVALSHA 命令并通过脚本的 SHA1 哈希值来执行脚本。

假设你有一个 Redis 键 mykey,并且你想使用 Lua 脚本获取该键的值并打印出来。

使用 SCRIPT 命令加载 Lua 脚本

SCRIPT LOAD "local key = KEYS[1]; local value = redis.call('GET', key); if value then return value; else return 'Key does not exist'; end"

"d3c21d0c2b9ca22f82737626a27bcaf5d288f99f"

使用 EVALSHA 命令执行脚本

-- 假设你已经知道脚本的 SHA1 哈希值,可以直接使用 `EVALSHA` 命令执行脚本。
redis-cli EVALSHA <SHA1_HASH> 1 mykey
-- 使用示例
 EVALSHA d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 key
"hello"

如果需要再lua脚本中加载脚本,可以使用SCRIPT LOAD命令。

-- 加载脚本
local script_sha = redis.call("SCRIPT", "LOAD", "script")
-- 执行脚本
local result = redis.call("EVAL", script_sha, 1, "key")
-- 返回结果
return result

http://www.kler.cn/a/413431.html

相关文章:

  • 道可云人工智能元宇宙每日资讯|第三届京西地区发展论坛成功召开
  • Linux之网络基础
  • 开源加密库mbedtls及其Windows编译库
  • JAVA篇05 —— 内部类(Local、Anonymous、Member、Static)
  • localStorage缓存 接口 配置
  • 实时数据开发 | checkpoints监控和调优
  • coqui-ai TTS 初步使用
  • React的基本知识:事件监听器、Props和State的区分、改变state的方法、使用回调函数改变state、使用三元运算符改变state
  • 命令行版 postman 之 post 小工具
  • TDengine 签约深圳综合粒子,赋能粒子研究新突破
  • Spring Boot Web应用开发:安全
  • docker安装使用Elasticsearch,解决启动后无法访问9200问题
  • 基于Java Springboot智慧养老院管理系统
  • 生产环境中,nginx 最多可以代理多少台服务器,这个应该考虑哪些参数 ?怎么计算呢
  • Stable Diffusion初步见解(二)
  • Spring:AOP面向切面编程入门案例
  • 如何提升C/C++的编程能力
  • 临床检验项目指标学习笔记
  • c++小球反弹可视化
  • Layui弹窗之图表绘制
  • 安全设备-日志审计-数据管理
  • 如何利用python爬虫获得店铺的所有商品
  • 使用client-go在命令空间test里面对pod进行操作
  • 云计算的计算包括哪些内容
  • JVM 性能调优 -- CMS 垃圾回收器 GC 日志分析【Full GC】
  • tongweb安全整改(by cz)