在請求返回后繼續(xù)執(zhí)行章節(jié)中,我們介紹了一種實現(xiàn)的方法,這里我們
介紹一種更優(yōu)雅更通用的方法:ngx.timer.at()。
ngx.timer.at
會創(chuàng)建一個 Nginx timer。在事件循環(huán)中,Nginx 會找出到期的 timer,并在一個獨立的協(xié)程中執(zhí)行對應(yīng)的 Lua 回調(diào)函數(shù)。
有了這種機制,ngx_lua 的功能得到了非常大的擴展,我們有機會做一些更有想象力的功能出來。比如
批量提交和 cron 任務(wù)。隨便一提,官方的 resty-cli
工具,也是基于 ngx.timer.at
來運行指定的代碼塊。
比較典型的用法,如下示例:
local delay = 5
local handler
-- do some routine job in Lua just like a cron job
handler = function (premature)
if premature then
return
end
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
end
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
從示例代碼中我們可以看到,ngx.timer.at
創(chuàng)建的回調(diào)是一次性的。如果要實現(xiàn)“定期”運行,需要在回調(diào)函數(shù)中重新創(chuàng)建 timer 才行。不過當(dāng)前主線上的 OpenResty 已經(jīng)引入了新的 ngx.timer.every
接口,允許直接創(chuàng)建定期執(zhí)行的 timer。
ngx.timer.at
的 delay 參數(shù),指定的是以秒為單位的延遲觸發(fā)時間。跟 OpenResty 的其他函數(shù)一樣,指定的時間最多精確到毫秒。如果你想要的是一個當(dāng)前階段結(jié)束后立刻執(zhí)行的回調(diào),可以直接設(shè)置 delay
為 0。
handler 回調(diào)第一個參數(shù) premature,則是用于標(biāo)識觸發(fā)該回調(diào)的原因是否由于 timer 的到期。Nginx worker 的退出,也會觸發(fā)當(dāng)前所有有效的 timer。這時候 premature 會被設(shè)置為 true。回調(diào)函數(shù)需要正確處理這一參數(shù)(通常直接返回即可)。
需要特別注意的是:有一些 ngx_lua 的 API 不能在這里調(diào)用,比如子請求、ngx.req.*和向下游輸出的 API(ngx.print、ngx.flush 之類),原因是這些調(diào)用需要依賴具體的請求。但是 ngx.timer.at
自身的運行,與當(dāng)前的請求并沒有關(guān)系的。
再說一遍,ngx.timer.at
的執(zhí)行是在獨立的協(xié)程里完成的。千萬不能忽略這一點。有人可能會犯這樣的錯誤:
local tcpsock = create_tcp_client() -- 創(chuàng)建一個 cosocket 連接
local ok, err = ngx.timer.at(delay, function()
tcpsock:send() -- bad request!
end)
cosocket 跟某個特定的 ngx_http_request_t*
綁定在一起的。雖然由于閉包,在回調(diào)函數(shù)中我們依舊可以訪問 tcpsock
,但整個上下文已經(jīng)不一樣了。