本文目标
- 学习lua基本语法
- 能够采用redis+lua
lua 基本语法
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 特性
- 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
- 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
- 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
- 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
- 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
- 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
基本语法
-- 行注释--[[块注释--]]--全局num = 3-- 变量a1 = 5local a2 = 6function fun1() a3 = 7 local a4 = 8 --局部变量endprint("1.----变量分为:全局变量和局部变量")print(a1, a2, a3, a4)print("2.----循环与控制语句")b1 = 1while (b1 num)--[[ if 语句 Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。 !boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是 false,其他的都为 true,数字 0 也是 true:--]]if false or nil then print("至少有一个是 true")else print("false 和 nil 都为 false")endif 0 then print("数字 0 是 true")else print("数字 0 为 false")end--[[运算符1. +加、 -减、 *乘、 /除、 %取余、 ^乘幂、 -负数2. ==等于、【~=】不等于、>、=、<=3. and、or、【not】 --]]print("---------分割线---------")e1 = truee2 = trueif (e1 and e2) then print("e1 and e2 - 条件为 true")endif (e1 or e2) then print("e1 or e2 - 条件为 true")endprint("---------分割线---------")-- 修改 a 和 b 的值e1 = falsee2 = trueif (e1 and e2) then print("e1 and e2 - 条件为 true")else print("e1 and e2 - 条件为 false")endif (not (e1 and e2)) then print("not( e1 and e2) - 条件为 true")else print("not( e1 and e2) - 条件为 false")endprint("---------函数---------")myprint = function(params) print("函数 ##", params, "##")endfunction add(num1, num2, functionPrint) sum = num1 + num2 functionPrint(sum)endadd(1, 3, myprint)function maximun(array) local index = 1 local value = array[index] for i, v in ipairs(array) do if v > value then index = i value = v end end return index, valueend-- !Lua的下标不是从0开始的,是从1开始的。print(maximun({800, 19, 1, 4, 8, 102}))-- 可变参数 三点 ... 表示函数有可变的参数function add(...) local sum = 0 for i,v in ipairs{...} do sum = sum + v end -- select("#",...) 来获取可变参数的数量 -- .. 字符串拼接 print("总共传入 " .. select("#",...) .. " 个数") return sumendprint(add(1,2,3,4,5))-- 斐波那契数列function fib(n) if n<2 then return 1 end return fib(n-2) + fib(n+1)end--[[ 闭包--]]function newCounter() local i=0 return function() i= i+1 return i endendnewCounter = newCounter()newCounter()newCounter()--[[ 函数返回值,多个--]]function getUserInfo(id) print(id) return "haoel", 37, "haoel@hotmail.com", "https://coolshell.cn"end-- 似乎必须直接解构!!!name, age, email, website, bGay = getUserInfo()userInfo = getUserInfo()-- haoel 37 haoel@hotmail.com https://coolshell.cn nilprint(name, age, email, website, bGay)-- haoelprint(userInfo)--[[ 函数返回值,多个--]]print("---------函数---------")print("---------table 类型---------")--[[ table 类型--]]mytable = {}-- table 里面值的设置和获取mytable[1] = "元素1"mytable["er"] = "元素2"print(mytable[1])-- 数组,lua里面的元素是从 1 开始的array = {10,20,30,40,50}--[[ 等价于 array = {[1]=10, [2]=20, [3]=30, [4]=40, [5]=50}--]]-- 数组里面值得获取print(array[1], array[2], array[3])-- 字典dictionary = { key1 = "value1", key2 = "value2", key3 = "value3"}-- 字典里面值得获取print(dictionary.key1, dictionary.key2, dictionary.key3)print("---------table 类型---------")
Redis + Lua
在 Redis 中,执行 Lua 语言是原子性的,有助于 Redis 对并发数据一致性的支持。
为什么要用lua
- 减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。
- 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他进程或者进程的命令插入。(最重要)
- 复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。
两种方法运行脚本
- 直接输入一些 Lua 语言的程序代码;简单的脚本可以直接采用这种
- 将 Lua 语言编写成文件。有一定逻辑的采用这种
基本命令
eval lua-script key-num [key1 key2 key3 ...] [value1 value2 value3 ...]
其中:
- eval 代表执行 Lua 语言的命令。
- Lua-script 代表 Lua 语言脚本。
- key-num 整数代表参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。
- [key1 key2 key3…] 是 key 作为参数传递给 Lua 语言,也可以不填它是 key 的参数,但是需要和 key-num 的个数对应起来。
- [value1 value2 value3…] 这些参数传递给 Lua 语言,它们是可填可不填的。
实例一 嵌入脚本
基本用法:set、get
# set127.0.0.1:6379> EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 lua-key lua-valueOK# get127.0.0.1:6379> EVAL "return redis.call('get', KEYS[1])" 1 lua-key"lua-value"127.0.0.1:6379> lpush person a b c(integer) 3# 多参数127.0.0.1:6379> EVAL "return redis.call('lrange', KEYS[1], ARGV[1], ARGV[2])" 1 person 0 -11) "c"2) "b"3) "a"
字段说明
- KEYS[1]: 需要大写,对应的是1之后的lua-key, 【占位符】
- 1: 代表之后key的个数,多余的舍弃
- ARGV[1]: 需要大写,对应的是value的第一个
注意事项
- KEYS、ARGV:需要大写
- 内部需要用单引号
node代码
// 嵌入脚本async function fun1() { const argv = ['lua-key', 'lua-value']; const set = await redis.", 1, argv); const get = await redis.", 1, argv[0]); console.log("简单:", set, get); // 同时传入多个key需要借助lua中的循环 // const list = await redis.", 1, 'list', 1,2,3); await redis.", 1, 'list', '1'); const listGet = await redis.", 1, 'list', 0, -1); console.log("队列:", listGet)}
const evalScript = `return redis.call('SET', KEYS[1], ARGV[2])`;// 缓存脚本async function fun2() { // 1. 缓存脚本获取 sha1 值 const sha1 = await redis.script("load", evalScript); console.log(sha1); // 6bce4ade07396ba3eb2d98e461167563a868c661 // 2. 通过 evalsha 执行脚本 await redis.evalsha(sha1, 2, 'name1', 'name2', 'val1', 'val2'); // 3. 获取数据 const result = await redis.get("name1"); console.log(result); // "val2"}
实例二 脚本文件 -- 频次限制
// 脚本文件async function fun3() { const luaScript = fs.readFileSync('./limit.lua'); const key = 'rate:limit'; // @ts-ignore const limit = await redis.; console.log('limit', limit);}
--[[Author: simutyDate: 2020-11-26 11:17:33LastEditTime: 2020-11-26 13:50:03LastEditors: Please set LastEditorsDescription:limit.lua!10秒内只能访问3次。 后续该脚本可以在nginx或者程序运行脚本中直接使用,判断返回是否为0,就0就不让其继续访问。!以上,如果不使用redis+lua,那高并发下incr和expire就会出现原子性破坏,造成expire执行多次浪费--]]local times = redis.call("incr", KEYS[1])if times == 1 then redis.call("expire", KEYS[1], ARGV[1])else if times > tonumber(ARGV[2]) then return 0 endendreturn 1
➜ 6Lua git:(main) ✗ ts-node index.tslimit 1limit 1limit 1limit 0limit 0
实例三 脚本文件 -- 自增ID
local key = KEYS[1]local id = redis.call("get", key)if id == false then redis.call("set", key, 1) return key .. "00001"else redis.call("set", key, id + 1) return key .. string.format("%04d", id + 1)end