0%

《OpenResty精华整理》4.一些特性和用法

一些特性和用法

同步非阻塞


非阻塞调用

非阻塞调用下的同步和异步

除了 ngx.timer 等少数异步操作外,使用 OpenResty 只需要编写同步的代码就可以,而不需要类似 Nginx 源码那样编写很多异步的代码。

文档格式一致

/t 目录,它里面就是所有的测试案例

shared dict

shared dict 可以完成 worker 间的数据共享,并借此实现 worker 之间的通信

lua-nginx-module vs lua-resty-core

对于 OpenResty 的实现,可能大部分的认识都是 OpenResty 是基于 lua-nginx-module 实现的,而不知道 lua-resty-core 这个项目,实际上,官方后续的计划便是把越来越多的功能采用 lua-resty-core 重新实现。

在核心的 lua-nginx-module 中,调用 C 函数的 API,都是使用 Lua C API 来完成的;

而在 lua-resty-core 中,则是把 lua-nginx-module 已有的部分 API,使用 FFI 的模式重新实现了一遍。

如果比较关注 OpenResty 新版特性 的话,可以发现一些关于 lua-resty-core 的说明,比如基于 lua-resty-core 实现了更多的功能等等,都是基于这个目的,至于原因下面会讲到。

需要澄清下,lua-nginx-module 提供的 API,是通过 C 实现,并通过 Lua CFunction 暴露出来的。

而 lua-resty-core 提供的 API,也不是表面看上去那样用 Lua 实现的,是通过在 lua-nginx-module 里面的以 lua_ffi 形式命名的 C 函数实现的,并在 lua-resty-core 里面通过 LuaJIT FFI 暴露出来的。

所以其实两者都是 C 实现。两者的比较,应该是 Lua CFunction 和 LuaJIT FFI 的比较。

Lua CFunction

以 ngx.base64_decode 举例,lua-nginx-module 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int
ngx_http_lua_ngx_decode_base64(lua_State *L)
{
ngx_str_t p, src;

src.data = (u_char *) luaL_checklstring(L, 1, &src.len);

p.len = ngx_base64_decoded_length(src.len);

p.data = lua_newuserdata(L, p.len);

if (ngx_decode_base64(&p, &src) == NGX_OK) {
lua_pushlstring(L, (char *) p.data, p.len);

} else {
lua_pushnil(L);
}

return 1;
}

用 C 编写的函数,无法把返回值传给 Lua 代码,而是需要通过栈,来传递 Lua 和 C 之间的调用参数和返回值。

同时,这些代码也不能被 JIT 跟踪到,所以对于 LuaJIT 而言,这些操作是处于黑盒中的,没法进行优化。

LuaJIT FFI

FFI 的交互部分是用 Lua 实现的,这部分代码可以被 JIT 跟踪到,并进行优化;
当然,代码也会更加简洁易懂。

以 ngx.base64_decode 举例,lua-resty-core 实现 https://github.com/openresty/lua-resty-core/blob/master/lib/resty/core/base64.lua#L60

1
2
3
4
5
6
7
8
9
10
11
12
ngx.decode_base64 = function (s)
local slen = #s
local dlen = base64_decoded_length(slen)

local dst = get_string_buf(dlen)
local pdlen = get_size_ptr()
local ok = C.ngx_http_lua_ffi_decode_base64(s, slen, dst, pdlen)
if ok == 0 then
return nil
end
return ffi_string(dst, pdlen[0])
end

值得说明的几个点

  • FFI + JIT 在跟 C 领域频繁数据交互的领域才明显,例如:

    • ngx.md5 相差不大
    • ngx.ctx, ngx.re.find, ngx.time 效果相差几十倍
  • 解释模式下 LuaJIT 的 FFI 操作很慢,比编译模式下慢十倍。
    调用 Lua CFunction 会迫使 LuaJIT 退回到解释模式,而通过 FFI 调用 C 函数则不会。

  • 除了不会打断 tracing,FFI 实现的版本还有另一个优势:LuaJIT 能够在编译时优化 FFI 实现代码。

  • 在 OpenResty 2019 年 5 月份发布的 1.15.8.1 版本前,lua-resty-core 默认是不开启的,而这不仅会带来性能损失,更严重的是会造成潜在的 bug。

    强烈推荐还在使用历史版本的用户,都手动开启 lua-resty-core。只需要在 init_by_lua 阶段,增加一行代码就可以了:

1
require "resty.core"

       1.15.8.1 版本中,增加了 lua_load_resty_core 指令,可以选择开启或关闭加载 lua-resty-core(默认开启)。

       1.17.8.1 版本后,lua_load_resty_core 指令被废除,lua-resty-core 是强制加载的,这对于开源项目来说是一个很好的决定,在没有什么理由选择更糟糕的实现的时候,强制帮助用户选择好,防止给不熟悉的用户更多的疑惑性选择。

  • OpenResty 中的函数都是有命名规范的,可以通过命名推测出它的用处。

    比如:

        ngx_http_lua_ffi_ ,是用 FFI 来处理 NGINX HTTP 请求的 Lua 函数;

        ngx_http_lua_ngx_ ,是用 Cfunction 来处理 NGINX HTTP 请求的 Lua 函数;

        其他 ngx_ 和 lua_ 开头的函数,则分别属于 NGINX 和 Lua 的内置函数。

ngx.ctx

ngx.ctx 可以在同一个请求的不同阶段之间共享数据。它其实就是一个普通的 Lua 的 table,所以速度很快,还可以存储各种 Lua 的对象。它的生命周期是请求级别的,当一个请求结束的时候,ngx.ctx 也会跟着被销毁掉。

定时任务

ngx.timer.at,用来执行一次性的定时任务;
ngx.time.every,用来执行固定周期的定时任务。

特权进程

我们都知道 Nginx 主要分为 master 进程和 worker 进程,其中,真正处理用户请求的是 worker 进程。

而 OpenResty 在 Nginx 的基础上进行了扩展,增加了特权进程:privileged agent。

特权进程很特别:

  • 它不监听任何端口,这就意味着不会对外提供任何服务;
  • 它拥有和 master 进程一样的权限,一般来说是 root 用户的权限,这就让它可以做很多 worker 进程不可能完成的任务;
  • 特权进程只能在 init_by_lua 上下文中开启;
  • 另外,特权进程只有运行在 init_worker_by_lua 上下文中才有意义,因为没有请求触发,也就不会走到content、access 等上下文去。

开启特权进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
init_by_lua_block {
local process = require "ngx.process"

local ok, err = process.enable_privileged_agent()
if not ok then
ngx.log(ngx.ERR, "enables privileged agent failed error:", err)
end
}


nginx: master process
nginx: worker process
nginx: privileged agent process

test::nginx

test::nginx 是 OpenResty 测试体系中的核心,OpenResty 本身和周边的 lua-rety 库,都是使用它来组织和编写测试集的。

虽然它一个是测试框架,但它的门槛非常高。这是因为, test::nginx 和一般的测试框架不同,并非基于断言,也不使用 Lua 语言,这就要求开发者从零开始学习和使用 test::nginx,并得扭转自身对测试框架固有的认知。

test::nginx 糅合了 Perl、数据驱动以及 DSL(领域小语言)。对于同一份测试案例集,通过对参数和环境变量的控制,可以实现乱序执行、多次重复、内存泄漏检测、压力测试等不同的效果。