openresty 变量和进程

变量的范围

在 OpenResty 里面,只有在 init_by_lua*
 和 init_worker_by_lua*
 阶段才能定义真正的全局变量。 这是因为其他阶段里面,OpenResty 会设置一个隔离的全局变量表,以免在处理过程污染了其他请求。

在 openresty 中共享变量的范围

  1. ngx.shared.DICT 用于整个 nginx 的共享
  2. ngx.var 或者是模块的变量,对应到每个单独的 lua VM ,只能在单独的 worker 进程共享。
  3. 使用数据库来做全局的共享,比如 memecached 、redis 、mysql 等。

init 阶段初始化的变量和 init_worker 阶段的有什么区别。

init 在 Nginx fork 工作进程之前运行,因此此处加载的数据或者代码将在所有工作进程中享受到操作系统提供的写复制(COW)特点,

共享内存

1
2
-- lua_shared_dict dict size;
lua_shared_dict foo 1m; # 定一个名字为 foo 的 1m 的共享内存

定时器

ngx.timer.at 在指定的时间后运行

1
2
3
4
5
6
7
8
ok, err = ngx.timer.at(delay, handler, ...) -- 启动定时器,delay 秒后执行handler函数

local function handler(premature, ...) -- 定时器的回调函数
if premature then -- 检查进程是否处于退出阶段
return -- 最适当的收尾工作
end
... -- 任意的 Lua 代码
end

timer 阶段执行的回调函数 handler 是与前台请求处理完全分离的,所以在函数里就不能使用 ngx.var、ngx.req、ngx.ctx 和 ngx.print 等请求相关的函数,但其他的大多数功能都是可用的,比如 cosocker、共享内存等,所以可以利用定时器绕过 init_by_lua、log_by_lua、header_filter_by_lua 等指令的限制,在这些阶段里 “间接” 使用 cosocket 访问后端。

在 openresty 中,有很多优秀的库都是使用了共享内存和 worker 变量之间的同步,根据不同的场景形成不同的策略

例如:

  • lua-resty-counter
  • lua-resty-event
  • lua-resty-lmcache

进程管理

OpenResty 的进程模式基于 Nginx,通常以 master/worker 多进程方式提供服务,各个 worker 进程相互平等且独立,由 master 进程通过信号管理各个 worker 进程。

在这个基础上 OpenResty 做了一些很有使用价值的改进,新增一个拥有 root 权限的特权进程,worker 进程也可以有自己的唯一标识,一定程度上能够实现服务的自我感知自我管理。

进程管理功能主要位于 ngx.process 库,它是 lua-resty-core 库的一部分,必须显式加载之后才能使用,即:

1
local process = require "ngx.process"   -- 显式加载 ngx.process 库

进程类型

OpenResty 里的进程分为如下六种类型:

  • single:单一进程,即非 master/worker 模式;
  • master:监控进程,即 master 进程;
  • signaller:信号进程,即 “-s” 参数时的进程;
  • worker:工作进程,最常用的进程,对外提供服务;
  • helper:辅助进程,不对外提供服务,例如 cache 进程;
  • privileged agent:特权进程,OpenResty 独有的进程类型。

当前代码所在的进程类型可以用函数 ngx.process.type 获取,例如:

1
2
3
local process require "ngx.process"        -- 显示加载 ngx.process 库
local str = process.type() -- 获取当前的进程类型
ngx.say("type is", str) -- 通常就是 "worker"

特权进程

特权进程是一种特殊的 worker 进程,权限与 master 进程一致(通常就是 root),拥有其他 worker 进程相同的数据和代码,但关闭了所有的监听端口,不对外提供服务,像是一个 “沉默的聋子”。

特权进程必须显式调用函数 ngx.process.enable_privileged_agent 才能启用。而且只能在 “init_by_lua” 阶段里运行,通常的形式是:

1
2
3
4
5
6
7
init_by_lua_block {
local process = require "ngx.process"
local ok, err = process.enable_privileged_agent() -- 启动特权进程
if not ok then -- 检查是否启动成功
ngx.log(ngx.ERR, "failed: ", err)
end
}

因为关闭了所有的监听端口,特权进程不能接受请求,“rewrite_by_lua” “access_by_lua” “content_by_lua” “log_by_lua” 等请求处理相关的执行阶段没有意义,这些阶段里的代码在特权进程里都不会运行。

但有一个阶段是它可以使用的,那就是 “init_worker_by_lua”,特权进程要做的工作就是 ngx.timer.* 启动若干个定时器,运行周期任务,通过共享内存等方式与其他 worker 进程通信,利用自己的 root 权限做其他 worker 进程想做而不能做的工作。

例如可以用 get_master_pid 获取 master 进程的 pid,然后在特权进程里调用系统命令 kill 发送 SIGHUP/SIGQUIˇ/SIGUSRl 等信号,实现服务的自我管理。

作者

张巍

发布于

2023-04-20

更新于

2023-04-20

许可协议

评论