OpenResty:特權進程和定時任務


定時任務

在 OpenResty 中,有時候需要在后台定期地執行某些任務,比如同步數據、清理日志等。最容易想到的方法,便是對外提供一個 API 接口,在接口中完成這些任務;

然后用系統的 crontab 定時調用 curl,來訪問這個接口,進而曲線地實現這個需求。

不過,這樣會給運維帶來更高的復雜度。所以, OpenResty 提供了 ngx.timer 來解決這類需求。可以把ngx.timer看作是 OpenResty 模擬的客戶端請求,用以觸發對應的回調函數。

OpenResty 的定時任務可以分為下面兩種:

  • ngx.timer.at,用來執行一次性的定時任務;
  • ngx.time.every,用來執行固定周期的定時任務。

使用ngx.timer可以突破 init_worker_by_lua 中不能使用 cosocket 的限制。

下面這段代碼,啟動了一個延時為0的定時任務。它啟動了回調函數 handler,並在這個函數中,用 cosocket 去訪問一個網站:

init_worker_by_lua_block {
    local function handler()
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect(“www.baidu.com", 80)
    end
    
    local ok, err = ngx.timer.at(0, handler)
}

這樣,就繞過了 cosocket 在這個階段不能使用的限制。

ngx.timer.at 並沒有解決周期性運行這個需求,在上面的示例中,它是一個一次性的任務。

基於 ngx.timer.at 這個API,如果要做到周期性運行:

  • 可以在回調函數中,使用一個 while true 的死循環,執行完任務后 sleep 一段時間,自己來實現周期任務;
  • 你還可以在回調函數的最后,再創建另外一個新的 timer。

timer 的本質是一個請求,雖然這個請求不是終端發起 的;而對於請求來講,在完成自己的任務后它就要退出,不能一直常駐,否則很容易造成各種資源的泄漏。

第一種使用 while true 來自行實現周期任務的方案並不靠譜。第二種方案雖然是可行的,但遞歸地創建 timer ,並不容易理解。

OpenResty 后面新增的 ngx.time.every API,就是專門為了解決這個問題而出現的,它是更加接近 crontab 的解決方案。

在啟動了一個 timer 之后,就再也沒有機會來取消這個定時任務了,畢竟 ngx.timer.cancel 還是一個 todo 的功能。

就會面臨一個問題:定時任務是在后台運行的,並且無法取消;如果定時任務的數量很多,就很容易耗盡系統資源。

所以,OpenResty 提供了 lua_max_pending_timers 和 lua_max_running_timers 這兩個指令,來對其進行限制。前者代表等待執行的定時任務的最大值,后者代表當前正在運行的定時任務的最大值。

可以通過 Lua API,來獲取當前等待執行和正在執行的定時任務的值:

content_by_lua_block {
    ngx.timer.at(3, function() end)
    ngx.say(ngx.timer.pending_count())
}

這段代碼會打印出 1,表示有 1 個計划任務正在等待被執行。

content_by_lua_block {
    ngx.timer.at(0.1, function() ngx.sleep(0.3) end)
    ngx.sleep(0.2)
    ngx.say(ngx.timer.running_count())
}

這段代碼會打印出 1,表示有 1 個計划任務正在運行中。

 

特權進程

Nginx 主要分為 master 進程和 worker 進程,其中,真正處理用戶請求的是 worker 進程。可以通過 lua-resty-core 中提供的 process.type API ,獲取到進程的類型。

運行下面這個函數:

resty -e 'local process = require "ngx.process"
ngx.say("process type:", process.type())'

返回的結果不是 worker, 而是single。這意味 resty 啟動的 Nginx 只有 worker 進程, 沒有 master 進程。在 resty 的實現中,下面這樣的一行配置, 關閉了 master 進程:

master_process off;

而OpenResty 在 Nginx 的基礎上進行了擴展,增加了特權進程:privileged agent。特權進程很特別:

它不監聽任何端口,這就意味着不會對外提供任何服務;

它擁有和 master 進程一樣的權限,一般來說是 root 用戶的權限,這就讓它可以做很多 worker 進程不可能完成的任務;

特權進程只能在 init_by_lua 上下文中開啟;

特權進程只有運行在 init_worker_by_lua 上下文中才有意義,因為沒有請求觸發,也就不會走到content、access 等上下文去。

來看一個開啟特權進程的示例:

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
}

通過這段代碼開啟特權進程后,再去啟動 OpenResty 服務,可以看到,Nginx 的進程中多了特權進程:

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

特權進程不監聽端口,也就不能被終端請求觸發,那就只有使用gx.timer ,來周期性地觸發:

init_worker_by_lua_block {
    local process = require "ngx.process"
    local function reload(premature)
        local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r")
        if not f then
            return
        end
        local pid = f:read()
        f:close()
        os.execute("kill -HUP " .. pid)
    end
    if process.type() == "privileged agent" then
        local ok, err = ngx.timer.every(5, reload)
        if not ok then
            ngx.log(ngx.ERR, err)
        end
    end
}

上面這段代碼,實現了每 5 秒給 master 進程發送 HUP信號量的功能

非阻塞的 ngx.pipe

上面示例中,使用了 Lua 的標准庫,來執行外部命令行,把信號發送給了 master 進程,這種操作是會阻塞的。

os.execute("kill -HUP " .. pid)

lua-resty-shell 庫來調用命令行就是非阻塞的:

$  resty -e 'local shell = require "resty.shell"
> local ok, stdout, stderr, reason, status =
> shell.run([[echo "hello, world"]])
> ngx.say(stdout)'

hello, world

這段代碼可以算是 hello world 的另外一種寫法了,它調用系統的 echo 命令來完成輸出。類似的,可以 用 resty.shell ,來替代 Lua 中的 os.execute 調用。

lua-resty-shell 的底層實現,依賴了 lua-resty-core 中的 [ngx.pipe] API,所以,這個 使用 lua-resty-shell 打印出 hello wrold 的示例,改用 ngx.pipe ,可以寫成下面這樣:

$  resty -e 'local ngx_pipe = require "ngx.pipe"
> local proc = ngx_pipe.spawn({"echo", "hello world"})
> local data, err = proc:stdout_read_line()
> ngx.say(data)'

hello world

 

OpenResty 在做一個更好用的 Nginx 的前提下,也在嘗試往通用平台的方向上靠攏,希望開發者能夠盡量統一技術棧,都用 OpenResty 來解決開發需求。這對於運維來說是相當友好的,因為只要部署一個 OpenResty 就可以了,維護成本更低。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM