0%

lua和openrestry

参考OpenResty 最佳实践

一、前言

1 简介

Lua 从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言来设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用 ANSI C 编写,能以 C 程序库的形式嵌入到宿主程序中。LuaJIT 2 和标准 Lua 5.1 解释器采用的是著名的 MIT 许可协议。正由于上述特点,所以 Lua 在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域中得到了越来越广泛的应用。其中尤以游戏开发为最,许多著名的游戏,比如 Escape from Monkey Island、World of Warcraft、大话西游,都采用了 Lua 来配合引擎完成数据描述、配置管理和逻辑控制等任务。即使像 Redis 这样中性的内存键值数据库也提供了内嵌用户 Lua 脚本的官方支持。

2 特点

  1. 变量名没有类型,值才有类型,变量名在运行时可与任何类型的值绑定;
  2. 语言只提供唯一一种数据结构,称为表(table),它混合了数组、哈希,可以用任何类型的值作为 key 和 value。提供了一致且富有表达力的表构造语法,使得 Lua 很适合描述复杂的数据;
  3. 函数是一等类型,支持匿名函数和正则尾递归(proper tail recursion);
  4. 支持词法定界(lexical scoping)和闭包(closure);
  5. 提供 thread 类型和结构化的协程(coroutine)机制,在此基础上可方便实现协作式多任务;
  6. 运行期能编译字符串形式的程序文本并载入虚拟机执行;
  7. 通过元表(metatable)和元方法(metamethod)提供动态元机制(dynamic meta-mechanism),从而允许程序运行时根据需要改变或扩充语法设施的内定语义;
  8. 能方便地利用表和动态元机制实现基于原型(prototype-based)的面向对象模型;
  9. 从 5.1 版开始提供了完善的模块机制,从而更好地支持开发大型的应用程序;

3. lua和luaJIT

lua本身是一门脚本语言,可以直接运行,但是还有人觉得不够快,所以开发了luaJIT(lua Just-in Time compiler),它可以将lua代码编译成机器码,从而提高运行速度。

二、安装

http://openresty.org/en/

1. windows

2. linux

2.1. archlinux

1
2
# 一个是lua,一个是luajit
sudo pacman -S lua luajit

三、语法

1. 注释

1
2
3
4
5
-- 单行注释

--[[
多行注释
]]

2. 数据类型

1
2
3
4
5
print(type("hello world")) -->output:string
print(type(print)) -->output:function
print(type(true)) -->output:boolean
print(type(360.0)) -->output:number
print(type(nil)) -->output:nil

2.1. nil

1
2
local a
print(a) -->output:nil

2.2. boolean

  • 使用if进行判断的时候,nil和false才为假,其他所有值均为真,0和空字符串就是真
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local a = true
if a then
print("a") -->output:a
else
print("not a") --这个没有执行
end

local b = 0
if b then
print("b") -->output:b
else
print("not b") --这个没有执行
end

local c = nil
if c then
print("c") --这个没有执行
else
print("not c") -->output:not c
end

2.3. string

  • 先看示例
1
2
3
4
5
6
7
8
9
10
local s = string.char(0x30, 0x31, 0x32, 0x33, 0x00, 0x34, 0x01, 0x00)
local s2 = string.char(0x30, 0x31, 0x32, 0x33, 0x00, 0x34, 0x01, 0x00)
local n = string.len(s)
local n1 = #s
print(s) -->output:01234
print(n) -->output:8
print(n1) -->output:8
print(string.byte(s, 1, n)) -->output:4849505105210
print(s == '01234') -->output:false
print(s == s2) -->output:true
  • 打印时,不可见字符会跳过包括其他语言认为的\0
  • string.len统计的是所有长度,不是到\0截止,#也是一样
  • 虽然打印是01234,但是字符串对比时,还是会计算所有字节进行对比

2.4. table 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
local new_tab = require "table.new"
---------- 创建 ----------
local arr = new_tab(3, 0)

---------- 查询 ----------
local arr = new_tab(3, 0)
-- 长度,统计的是最后一个非nil值的位置
print(#arr) --> 0
-- 某一个值
print(arr[1]) --> nil

---------- 插入 ----------
local arr = new_tab(3, 0)
table.insert(arr, 1) -- 插入到最后一个非nil的后面
print(#arr) --> 1
print(arr[1]) --> 1

---------- 修改 ----------
local arr = new_tab(3, 0)
arr[1] = 1
print(#arr) --> 1
arr[4] = 3 -- 长度变成4
print(#arr) --> 4
arr[5] = nil -- 修改为nil没用
print(#arr) --> 4
arr[4] = nil -- 修改为nil后,长度只剩下1了
print(#arr) --> 1
arr[4] = 3 -- 长度变成4
table.insert(arr, 2) -- 插入到最后一个非nil的后面,也就是第5个位置
print(#arr) --> 5
print(arr[1]) --> 1
print(arr[2]) --> nil
print(arr[3]) --> nil
print(arr[4]) --> 3
print(arr[5]) --> 2

---------- 删除 ----------

2.5. table 哈希表

3. 实现一个类

3.1. 可继承类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- obj.lua
local _M = {}

-- 类方法
function _M:set_test(a)
self.test = a
end

-- 类方法
function _M:get_test()
local t = self.test
return t
end

-- 构造函数
function _M:new()
-- 默认属性
local _obj = {
test = 1
}
setmetatable(_obj, self)
self.__index = self
return _obj
end

return _M

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local obj = require "obj"

local _M = {}

function _M:run()
local a = obj:new()
a:set_test(2)
print(a:get_test())
local b = obj:new()
b:set_test(3)
print(a:get_test())
print(b:get_test())
end

return _M

3.2. 禁止继承的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
local _M = {}

-- 类方法
function _M:set_test(a)
self.test = a
end

-- 类方法
function _M:get_test()
local t = self.test
return t
end

local mt = { __index = _M }

-- 构造函数
function _M.new()
-- 默认属性
local _obj = {
test = 1
}
setmetatable(_obj, mt)
return _obj
end

return _M

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local obj = require "obj"

local _M = {}

function _M:run()
local a = obj.new()
a:set_test(2)
print(a:get_test())
local b = obj:new()
b:set_test(3)
print(a:get_test())
print(b:get_test())
end

return _M

4. 函数

4.1. 传参

  • 除table外,其他都是按值传递,table可以理解为传入了一个指针的值

4.2. 变长参数

1
2
3
4
5
6
7
8
9
10
11
local function test(a, ...)
local args = {...}
print(a, " ", args[1], " ", table.unpack(args, 1, #args))
end

function _M:run()
test(1) --> 1 nil
test(1, 2, 3) --> 1 2 23
test(1, nil, 3) --> 1 nil nil3
test(1, nil, 3, nil) --> 1 nil nil3
end

5. 内置函数

5.1. collectgarbage 手动GC

1
collectgarbage("collect")

四、系统库

1. string

1.1. string.byte

1
2
function string.byte(s: string|number, i?: integer, j?: integer)
-> ...integer
  • 返回字符串s中从ij的字节
  • 如果ij都没有指定,默认为1
  • 如果j没有指定,默认与i相等
1
2
3
print(string.byte("abc", 1, 3))     -->output:979899
print(string.byte("abc", 3)) -->output:99
print(string.byte("abc")) -->output:97
  • 返回的是一个变长列表,取值需要一个一个取,一个变量只能取第一个
1
2
3
4
5
6
7
8
local a, b, c = string.byte("abc", 1, 3)
print(a) -- 97
print(b) -- 98
print(c) -- 99

local d = string.byte("abc", 1, 3)
print(d) -- 97
print(type(d)) -- number

2. ffi 和C进行交互

https://luajit.org/ext_ffi_api.html

2.1. 原理

  • 使用c定义的符号,在lua中可以直接调用,需要注意的是lua的类型和c类型的互转

2.2. 类型转换

lua类型c类型备注
常量字符串const char *虽然可以用char *接,但是修改了会出问题
使用#取到的长度size_t
stringconst u_char *
numberint

2.3. 实例

1) printf的声明和调用

  • lua不支持和c交互不定参数,每个参数必须明确类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local _M = {}

local ffi = require "ffi"

ffi.cdef[[
int printf(const char *fmt, ...);
]]

function _M:run()
-- 不能带参数,参数是另外的机制进行映射
ffi.C.printf("hello world\n")

ffi.C.printf("hello world %d %d %s %x\n", 1, 2, "aaa", 0x123) --> hello world -334178024 0 (null) 0
end

return _M
  • 使用明确类型就可以正常使用
1
2
3
4
5
6
7
8
9
10
11
12
13
local _M = {}

local ffi = require "ffi"

ffi.cdef[[
int printf(const char *fmt, int a, int b);
]]

function _M:run()
ffi.C.printf("hello world, %d %d\n", 1, 2) --> hello world 1 2
end

return _M

2) 字符串传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
local _M = {}

local ffi = require "ffi"
local base = require "resty.core.base"

local C = ffi.C
local ffi_str = ffi.string
local get_string_buf = base.get_string_buf

ffi.load("/usr/local/openresty/nginx/conf/lua/ffi-libs/libmain.so", true)

ffi.cdef[[
int test(u_char * data, size_t n);
]]

function _M:run()
local buffer_size = 10
local str = get_string_buf(buffer_size)
ffi.copy(str, 'abcdef')
C.test(str, buffer_size)
local a = ffi_str(str, buffer_size)
print(a) --> bcdef
local b = 'abcde'
print(b) --> bcdef
end

return _M
  • 字符串出参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
local _M = {}

local ffi = require "ffi"
local base = require "resty.core.base"

local C = ffi.C
local ffi_str = ffi.string
local get_string_buf = base.get_string_buf
local get_size_ptr = base.get_size_ptr

ffi.load("/usr/local/openresty/nginx/conf/lua/ffi-libs/libmain.so", true)

ffi.cdef[[
int test(u_char *out, size_t *out_size);
]]

function _M:run()
local buffer_size = 10
local str = get_string_buf(buffer_size)
local size = get_size_ptr()
size[0] = buffer_size
C.test(str, size)

local a = ffi_str(str, size[0])
print(a) --> bcdef
end

return _M

3. bit 位操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
local bit = require("bit")

local ngx = ngx
local log = ngx.log

local NGX_NOTICE = ngx.NOTICE

local _M = {}

function _M:run()
local a = 1
-- 左移
a = bit.lshift(a, 2)
log(NGX_NOTICE, a) --> 1<<2 = 0b100 = 4
-- 右移
a = bit.rshift(a, 1)
log(NGX_NOTICE, a) --> 0b100>>1 = 0b010 = 2
-- 或
a = bit.bor(a, bit.lshift(1, 3))
log(NGX_NOTICE, a) --> 0b010|0b1000 = 0b1010 = 10
-- 与
a = bit.band(a, 1)
log(NGX_NOTICE, a) --> 0b1010&0b0001 = 0b0000 = 0
end

return _M

踩坑记

1) 不能把string当出参进行传递

1
2
3
4
5
6
7
8
9
10
#include <sys/types.h>

#define EXPORT_API __attribute__((visibility("default")))

EXPORT_API int test(u_char * const data, size_t n) {
for (int i = 0; i < n; i++) {
data[i]++;
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local _M = {}

local ffi = require "ffi"

ffi.load("/usr/local/openresty/nginx/conf/lua/ffi-libs/libmain.so", true)

ffi.cdef[[
int test(const u_char * data, size_t n);
]]

function _M:run()
local a = 'abcde'
print(a) --> abcde
ffi.C.test(a, #a)
print(a) --> bcdef
local b = 'abcde'
print(b) --> bcdef
end

return _M
  • string对lua来说是常量,不能修改的,强行用c改了后,lua其他地方使用同样的字符串的地方也会被修改掉

五、命令

1. 查看lua查找包的路径

1
lua -e "print(package.path)"

六、工具函数

1. 协程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
-- 依托于ngx的单线程多协程模型下的协程池,如果是多线程情况下不适用
local new_tab = require "table.new"
local semaphore = require "ngx.semaphore"

local ngx = ngx

local unpack = unpack

local _M = {}

-- 认为chan是单线程多协程运行的,所以这里不加锁
local chan = {
new = function(self)
local chan_attrs = {
_buffer = nil,
_has_value = false,
_write_sema = semaphore.new(), -- 可写信号量
_waiting_producer = 0, -- 在这个channel上等待的生产者数量
_read_sema = semaphore.new(), -- 可读信号量
_waiting_consumer = 0, -- 在这个channel上等待的消费者数量
}
return setmetatable(chan_attrs, { __index = self })
end,
send = function(self, value)
-- 如果当前有值,就等待消费者消费完毕
if self._has_value == true then
self._waiting_producer = self._waiting_producer + 1
while self._has_value == true do
-- 有消费者在等待,就post一个可读信号量
if self._waiting_consumer > 0 then
self._read_sema:post()
end
self._write_sema:wait(1)
end
self._waiting_producer = self._waiting_producer - 1
end

self._buffer = value
self._has_value = true

-- 有消费者在等待,就post一个可读信号量
if self._waiting_consumer > 0 then
self._read_sema:post()
end
end,
receive = function(self)
-- 如果当前没值,等待生产者写入
if self._has_value == false then
self._waiting_consumer = self._waiting_consumer + 1
while self._has_value == false do
-- 有生产者在等待,就post一个可写信号量
if self._waiting_producer > 0 then
self._write_sema:post()
end
self._read_sema:wait(1)
end
self._waiting_consumer = self._waiting_consumer - 1
end

local value = self._buffer
self._has_value = false

-- 有生产者在等待,就post一个可写信号量
if self._waiting_producer > 0 then
self._write_sema:post()
end
return value
end,
}

local function worker(funcCh)
while true do
local v = funcCh:receive()
if v == nil then
return
end
v.valueCh:send(v.func(unpack(v.args, 1, #v.args)))
end
end

function _M:run(func, ...)
local valueCh = chan:new()
self.tasks:send({
func = func,
args = {...},
valueCh = valueCh,
})
return valueCh
end

-- 构造函数,构造了一定要调用destroy
-- 销毁需要在content块退出前销毁,否则会造成content块退不掉而无法进入到log块
function _M:create(count)
count = count or 10
-- 默认属性
local _obj = {
pool_size = count,
tasks = chan:new(),
}
_obj.pool = new_tab(32, 0)
for i = 1, count, 1 do
_obj.pool[i] = ngx.thread.spawn(worker, _obj.tasks)
end

setmetatable(_obj, self)
self.__index = self
return _obj
end

-- 销毁
-- !!!销毁需要在content块退出前销毁,否则会造成content块退不掉而无法进入到log块!!!
function _M:destroy()
for i = 1, #self.pool, 1 do
self.tasks:send(nil)
end
end

return _M