一些特性和用法
同步非阻塞
除了 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 | static int |
用 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 | ngx.decode_base64 = function (s) |
值得说明的几个点
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 | init_by_lua_block { |
test::nginx
test::nginx 是 OpenResty 测试体系中的核心,OpenResty 本身和周边的 lua-rety 库,都是使用它来组织和编写测试集的。
虽然它一个是测试框架,但它的门槛非常高。这是因为, test::nginx 和一般的测试框架不同,并非基于断言,也不使用 Lua 语言,这就要求开发者从零开始学习和使用 test::nginx,并得扭转自身对测试框架固有的认知。
test::nginx 糅合了 Perl、数据驱动以及 DSL(领域小语言)。对于同一份测试案例集,通过对参数和环境变量的控制,可以实现乱序执行、多次重复、内存泄漏检测、压力测试等不同的效果。