【Lua】Lua Basic

Posted by 西维蜀黍 on 2022-01-23, Last Modified on 2022-02-19

Install

macOS

$ brew install lua

Demo

$ lua -i
Lua 5.4.3  Copyright (C) 1994-2021 Lua.org, PUC-Rio
>

Variables

--- 全局变量 
name = 'aaa'
--- 局部变量
local age = 18

-- Variable definition:
local a, b

-- Initialization
a = 10
b = 30

print("value of a:", a)

print("value of b:", b)

-----
local d , f = 5 ,10     --declaration of d and f as local variables. 
d , f = 5, 10;          --declaration of d and f as global variables. 
d, f = 10               --[[declaration of d and f as global variables. 
                           Here value of f is nil --]]

数据类型

  1. nil
  2. boolean 布尔值
  3. number 数字
  4. string 字符串
  5. table

boolean

number

table 类型

它既是数组,又是 map。

数组不分具体类型,演示如下

Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> arr_table = {'aa','bb',1}
> print(arr_table[1])
aa
> print(arr_table[3])
1
> print(#arr_table)
3

作为字典:

Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> arr_table = {name = 'aa', age = 18}
> print(arr_table['name'])
aa
> print(arr_table.name)
aa
> print(arr_table[1])
nil
> print(arr_table['age'])
18
> print(#arr_table)
0

或者

    a = {}     -- create a table and store its reference in `a'
    k = "x"
    a[k] = 10        -- new entry, with key="x" and value=10
    a[20] = "great"  -- new entry, with key=20 and value="great"
    print(a["x"])    --> 10
    k = 20
    print(a[k])      --> "great"
    a["x"] = a["x"] + 1     -- increments entry "x"
    print(a["x"])    --> 11

混合模式:

Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> arr_table = {'aa','Felordcn',1,age = 18,nil}
> print(arr_table[1])
aa
> print(arr_table[4])
nil
> print(arr_table['age'])
18
> print(#arr_table)
3

Ref

Insert and Remove

local a = {}
table.insert(a, "aaa")
table.insert(a, "bbb")
return a

-- output:
1) "aaa"
2) "bbb"

https://www.lua.org/pil/19.2.html

Get Len

local a = {}
table.insert(a, "aaa")
table.insert(a, "bbb")
table.getn(a)

String

Lua denotes the string concatenation operator by “..” (two dots). If any of its operands is a number, Lua converts that number to a string.

print("Hello " .. "World")  --> Hello World
print(0 .. 1)               --> 01

Remember that strings in Lua are immutable values. The concatenation operator always creates a new string, without any modification to its operands:

a = "Hello"
print(a .. " World")   --> Hello World
print(a)               --> Hello

Ref

Flows

If

if op == "+" then
    r = a + b
elseif op == "-" then
    r = a - b
elseif op == "*" then
    r = a * b
elseif op == "/" then
    r = a / b
else
    error("invalid operation")
end

For

local arr = { 1, 2, name = 'aaa' }

for i, v in ipairs(arr) do
    print('i = ' .. i)
    print('v = ' .. v)
end

print('-------------------')

for i, v in pairs(arr) do
    print('p i = ' .. i)
    print('p v = ' .. v)
end

-- output:
i = 1
v = 1
i = 2
v = 2
-----------------------
p i = 1
p v = 1
p i = 2
p v = 2
p i = name
p v = aaa

Lua in Redis

EVAL命令

Redis中使用EVAL命令来直接执行指定的Lua脚本。

EVAL luascript numkeys key [key ...] arg [arg ...]

Lua脚本中包括两组参数:KEYS[]ARGV[],两个数组下标从1开始。一个值得去遵守的最佳实践是:把redis操作所需的key通过KEYS进行参数传递,其他的Lua脚本所需的参数通过ARGV进行传递。

  • EVAL 命令的关键字。
  • luascript Lua 脚本。
  • numkeys 指定的Lua脚本需要处理键的数量,其实就是 key数组的长度。
  • key 传递给Lua脚本零到多个键,空格隔开,在Lua 脚本中通过 KEYS[INDEX]来获取对应的值,其中1 <= INDEX <= numkeys
  • arg是传递给脚本的零到多个附加参数,空格隔开,在Lua脚本中通过ARGV[INDEX]来获取对应的值,其中1 <= INDEX <= numkeys

Lua脚本通过各种语言的redis客户端都可以调用,我们就简单一点使用redis-cli

看下面的redis命令行:

eval "redis.call('set', KEYS[1], ARGV[1])" 1 key:name value

Demo

# case 1
127.0.0.1:6379> EVAL "return 'hello world'" 0
"hello world"

# case 2
127.0.0.1:6379> eval 'redis.call("hget","foo","bar")' 0
(error) ERR Error running script (call to f_9e6d82f0740926e0a70775430bda59a54d4e0664): ERR Operation against a key holding the wrong kind of value
127.0.0.1:6379> eval 'redis.pcall("hget","foo","bar")' 0
(nil)

# case 3
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> EVAL "return redis.call('GET',KEYS[1])" 1 hello
"world"
127.0.0.1:6379> EVAL "return redis.call('GET','hello')"
(error) ERR wrong number of arguments for 'eval' command
127.0.0.1:6379> EVAL "return redis.call('GET','hello')" 0
"world"
# If not return, we get nothing even if such a key exists
127.0.0.1:6379> EVAL "redis.call('GET','hello')" 0
(nil)

# case 4
# 设置hkeys为键Hash值
hmset hkeys key:1 value:1 key:2 value:2 key:3 value:3 key:4 value:4 key:5 value:5 key:6 value:6
# 建一个order为键的集合,并给出顺序
zadd order 1 key:3 2 key:1 3 key:2
eval "local order = redis.call('zrange', KEYS[1], 0, -1); return redis.call('hmget',KEYS[2],unpack(order));" 2 order hkeys

call函数和pcall函数

在上面的例子中我们通过redis.call()来执行了一个SET命令,其实我们也可以替换为redis.pcall()。它们唯一的区别就在于处理错误的方式,前者执行命令错误时会向调用者直接返回一个错误;而后者则会将错误包装为一个我们上面讲的table

127.0.0.1:6379> EVAL "return redis.call('no_command')" 0
(error) ERR Error running script (call to f_1e6efd00ab50dd564a9f13e5775e27b966c2141e): @user_script:1: @user_script: 1: Unknown Redis command called from Lua script
127.0.0.1:6379> EVAL "return redis.pcall('no_command')" 0
(error) @user_script: 1: Unknown Redis command called from Lua script

这就像Java遇到一个异常,前者会直接抛出一个异常;后者会把异常处理成JSON返回。

值转换

由于在Redis中存在Redis和Lua两种不同的运行环境,在Redis和Lua互相传递数据时必然发生对应的转换操作,这种转换操作是我们在实践中不能忽略的。例如如果Lua脚本向Redis返回小数,那么会损失小数精度;如果转换为字符串则是安全的。

127.0.0.1:6379> EVAL "return 3.14" 0
(integer) 3
127.0.0.1:6379> EVAL "return tostring(3.14)" 0
"3.14"

脚本管理

SCRIPT LOAD

预加载脚本到Redis以达到重复使用的效果,从而可以避免多次加载脚本导致的浪费带宽。

script load "return redis.call('get', KEYS[1])"

预加载完成之后,你会看到下面的一段输出

“4e6d8fc8bb01276962cce5371fa795a7763657ae”

这是一个具有唯一性的hash字符串,这个hash就代表着我们刚刚预加载的Lua脚本,我们可以通过EVALSHA命令执行该脚本。如:

evalsha 4e6d8fc8bb01276962cce5371fa795a7763657ae 1 key:name

执行的结果与下面的是一致的。

eval "return redis.call('get', KEYS[1])" 1 key:name

SCRIPT FLUSH

既然有缓存就有清除缓存,但是遗憾的是并没有根据SHA来删除脚本缓存,而是清除所有的脚本缓存,所以在生产中一般不会再生产过程中使用该命令。

SCRIPT EXISTS

以SHA标识为参数检查一个或者多个缓存是否存在。

127.0.0.1:6379> SCRIPT EXISTS 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b  1b936e3fe509bcbc9cd0664897bbe8fd0cac1012
1) (integer) 1
2) (integer) 0

SCRIPT KILL

终止正在执行的脚本。但是为了数据的完整性此命令并不能保证一定能终止成功。如果当一个脚本执行了一部分写的逻辑而需要被终止时,该命令是不凑效的。需要执行SHUTDOWN nosave在不对数据执行持久化的情况下终止服务器来完成终止脚本。

Debug

参数 –eval script key1 key2 , arg1 age2 这种模式,key和value用一个逗号隔开就好了,

[root@localhost Desktop]# redis-cli --eval /usr/redis/sbin/1.lua username age , jack 20
1) "username"
2) "age"
3) "jack"
4) "20"
[root@localhost Desktop]# 

Reference