openresty 中主要的几种异步执行方式
openresty宣传是同步非阻塞(100% non-blocking)的,基于事件通知的 Nginx 给我们带来了足够强悍的高并发支持。可以让我们可以使用同步的编程方式实现异步处理,但在我们难免在应用中会使用非openresty api的调用,比如
- 高 CPU 的调用(压缩、解压缩、加解密等)
- 高磁盘的调用(所有文件操作)
- 非 OpenResty 提供的网络操作(luasocket 等)
- 系统命令行调用(os.execute 等)
在使用异步方式执行代码的时候我们常会用到第三方工具,比如队列,存储。我在这里介绍几种openresty自带的异步api,讲究一个原汤化原食
脱离请求进程 - ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)
timer是一个计时器,他将在delay s 之后起一个轻线程,然后调用callback,user_arg1 ... 等是传入参数
定时器看起来很完美,但是有个缺点
- 在timer中无法使用cosocket
无法使用cosocket比较致命,在openresty中cosocket是核心中的核心,不能用就等于openresty废了一大半,所以我建议使用timer只做些简单的工作,比如保存文件到硬盘。
但如果一定要做一些比如网络请求,我们可以绕过cosocket,比如使用 resty.http库。
local ok, err = ngx.timer.at(0, function(_, _file_id,_file_data)
local args = {
func = 'erase_words_collection',
file_id = _file_id
}
curl('http://127.0.0.1/async/upload_file', 'POST', nil, _file_data, nil, args)
end, file_id, file_data)
if not ok then
nlog.error('async_util.upload_file: create timer error :'.. tostring(err))
end
另外openresty 也提供了指令和api 管理timer的数量
- lua_max_running_timers 指定最大运行的定时器数量(默认值为256)
- lua_max_pending_timers 指定最大等待运行的定时器的数量(默认值为1024)
- ngx.timer.running_count()
- ngx.timer.pending_count()
拆分请求 ngx.thread.*
协程是 openresty异步高效的最基础组件
如果是需要批量任务,那么使用协程是个不错的选择,但也要注意协程数量以及慎用wait any(可以参考我的上一篇文章)
local capture = ngx.location.capture
local spawn = ngx.thread.spawn
local wait = ngx.thread.wait
local say = ngx.say
local function fetch(uri)
return capture(uri)
end
local threads = {
spawn(fetch, "/foo"),
spawn(fetch, "/bar"),
spawn(fetch, "/baz")
}
for i = 1, #threads do
local ok, res = wait(threads[i])
if not ok then
say(i, ": failed to run: ", res)
else
say(i, ": status: ", res.status)
say(i, ": body: ", res.body)
end
end
提前返回响应 - ngx.eof()
看一个官网的例子
location = /async {
keepalive_timeout 0;
content_by_lua_block {
ngx.say("got the task!")
ngx.eof() -- well written HTTP clients will close the connection at this point
-- access MySQL, PostgreSQL, Redis, Memcached, and etc here...
}
}
作用一目了然,这里先完成http响应,然后在后完成比如mysql等存储的访问。
另外还提供了个指令,打开之后可以使链接关闭之后还保持子请求
proxy_ignore_client_abort on;
官网推荐最好使用 ngx.timer.at,但我觉得不如eof
A better way to do background jobs is to use the ngx.timer.at API.
总结
- 如果是期望请求结束后执行,比较复杂 用 ngx.eof
- 如果比较简单的异步任务,使用timer
- 如果是批量的任务 使用thread