0%

《OpenResty精华整理》5.需要注意的点

需要注意的点

时间 API

返回当前时间的 API,如果没有非阻塞网络 IO 操作来触发,便会一直返回缓存的值,而不是像我们想的那样,能够返回当前的实时时间。

1
2
3
$ resty -e 'ngx.say(ngx.now())
os.execute("sleep 1")
ngx.say(ngx.now())'

在两次调用 ngx.now 之间,使用 Lua 的阻塞函数 sleep 了 1 秒钟,但从打印的结果来看,这两次返回的时间戳却是一模一样的。
如果换成是非阻塞的 sleep 函数,它就会打印出不同的时间戳了。

1
2
3
$ resty -e 'ngx.say(ngx.now())
ngx.sleep(1)
ngx.say(ngx.now())'

json 编码时无法区分 array 和 dict

json 对空 table 编码的时候,无法确定编码为数组还是字典。

OpenResty 的 cjson 库,默认把空 table 当做字典来编码。我们可以通过 encode_empty_table_as_object 这个函数,来修改这个全局的默认值:

1
2
3
4
resty -e 'local cjson = require "cjson"
cjson.encode_empty_table_as_object(false)
local t = {}
print(cjson.encode(t))'

全局这种设置的影响面比较大,可以指定某个 table 的编码规则:

1
2
3
4
5
6
resty -e 'local cjson = require "cjson"
local t = {}
setmetatable(t, cjson.empty_array_mt)
print(cjson.encode(t))
t = {123}
print(cjson.encode(t))'

真值和空值

ngx.null

因为 Lua 的 nil 无法作为 table 的 value,所以 OpenResty 引入了 ngx.null,作为 table 中的空值。
只有 nil 和 false 是假值,ngx.null 的布尔值为真。如果遗漏了这一点,就很容易踩坑,比如在使用 lua-resty-redis 的时候,做了下面这个判断:

1
2
3
4
local res, err = red:get("dog")
if not res then
res = res + "test"
end

在 dog 这个 key 不存在的情况下,这段代码就 500 崩溃了。

cdata:NULL

当通过 LuaJIT FFI 接口去调用 C 函数,而这个函数返回一个 NULL 指针,那么就会遇到另外一种空值,即 cdata:NULL 。
和 ngx.null 一样,cdata:NULL 也是真值。但下面这段代码,会打印出 true,也就是说 cdata:NULL 是和 nil 相等的:

1
2
3
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
ngx.say(cdata_null == nil)'

cjson.null

Lua 中的 nil,被 json encode 和 decode 一圈儿之后,就变成了 cjson.null。它引入的原因和 ngx.null 是一样的,因为 nil 无法在 table 中作为 value。

1
2
3
4
$ resty -e 'local cjson = require "cjson"
local data = cjson.encode(nil)
local decode_null = cjson.decode(data)
ngx.say(decode_null == cjson.null)'

变量的个数限制

Lua 中,一个函数的局部变量的个数,和 upvalue 的个数都是有上限的,你可以从 Lua 的源码中得到印证:

1
2
3
4
5
6
7
8
9
10
11
12
/*
@@ LUAI_MAXVARS is the maximum number of local variables per function
@* (must be smaller than 250).
*/
#define LUAI_MAXVARS 200


/*
@@ LUAI_MAXUPVALUES is the maximum number of upvalues per function
@* (must be smaller than 250).
*/
#define LUAI_MAXUPVALUES 60

这两个阈值,分别被硬编码为 200 和 60。虽说你可以手动修改源码来调整这两个值,不过最大也只能设置为 250。

一般情况下,我们不会超过这个阈值,但写 OpenResty 代码的时候,还是要留意这个事情,不要过多地使用局部变量和 upvalue,而是要尽可能地使用 do .. end 做一层封装,来减少局部变量和 upvalue 的个数。

1
2
3
4
5
6
7
8
9
10
local re_find = ngx.re.find
function foo()
...
end
function bar()
...
end
function fn()
...
end

如果只有函数 foo 使用到了 re_find, 可以这样改造下:

1
2
3
4
5
6
7
8
9
10
11
12
do
local re_find = ngx.re.find
function foo()
...
end
end
function bar()
...
end
function fn()
...
end

这样一来,在 main 函数的层面上,就少了 re_find 这个局部变量。这在单个的大的 Lua 文件中,算是一个优化技巧。