天天看點

ngx_lua_API 詳解 (一)

ngx.thread.spawn、ngx.thread.wait、ngx.thread.kill 介紹

ngx_lua中通路多個第三方服務

ngx_lua中提供了ngx.socket API,可以友善的通路第三方網絡服務。如下面的代碼,通過get_response函數從兩個(或者更多)的源伺服器擷取資料,再生成響應發給用戶端。

location / {
    content_by_lua_block {
        local get_response(host, port)
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(host, port)
            if not ok then
                return nil, err
            end
            local data, err = sock:receive()
            if not data then
                return nil, err
            end

            return data
        end

        local first = get_response("lua.org", 8080)
        local second = get_response("nginx.org", 8080)
        ngx.say(first .. second)
    }
}      

如果需要10個第三方網絡服務,需要調用get_response 10次。總的響應時間與需要連接配接源的數量成正比。那麼如何縮短源的響應時間呢?ngx.thread就是用來解決這種問題的。

二、lua-nginx-module提供了三個API

  1、ngx.thread.spawn

  2、ngx.thread.wait

  3、ngx.thread.kill

三、詳解

1、ngx.thread.spawn

重點:ngx.thread.spawn生成新的"light thread",這個"light thread"運作優先級比它的父協程高,會優先運作,父協程被迫暫停。"light thread"運作結束或者yield後,再由ngx_http_lua_run_posted_threads去運作父協程。

​​參考:ngx_lua中的協程排程(六)之ngx_http_lua_run_posted_thread​​

通過ngx.thread.spawn可以生成一個"light thread",一個”light thread“和Lua的協程類似,差別在于"light thread"是由ngx_lua子產品進行排程的,多個"light thread"同時運作。

"light thread",協程 和 程序。"light thread"比Lua中的協程更像作業系統中的程序。

  • fork生成新的程序,生成的多個程序可以同時運作,而ngx.thread.spawn生成新的協程,多個協程同時在跑。
  • kill可以殺死不需要的子程序,ngx.thread.kill可以殺死不需要的"light thread"
  • wait可以等待子程序結束并取得子程序退出狀态,ngx.thread.wait可以等待"light thread"結束并擷取其傳回值。

ngx.thread的使用,用ngx.thread重寫上面的代碼

location / {
    content_by_lua_block {
        local get_response(host, port)
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(host, port)
            if not ok then
                return nil, err
            end
            local data, err = sock:receive()
            if not data then
                return nil, err
            end

            return data
        end

        local t1 = ngx.thread.spawn(get_response, "lua.org", 8080)
        local t2 = ngx.thread.spawn(get_response, "nginx.org", 8080)
        local ok, res1, res2 = ngx.thread.wait(t1, t2)
        ngx.say(res1 .. res2)
    }
}      

生成的兩個"light thread"可以同時運作,總的耗時隻相當于通路一個源伺服器的時間,即使需要通路的源伺服器增加,耗時沒有太大的變化。

"light thread"的排程

  Linux中的fork生成新的子程序,父程序與子程序誰先運作呢?都有可能,和系統的排程有關。

  把調用ngx.thread.spawn的這個Lua協程稱為父協程,生成的"light thread"和父協程誰先運作呢? 在ngx_lua的排程邏輯中,是生成的"light thread"先運作,運作結束或者被挂起後,父協程才會繼續運作。實際的代碼在ngx_http_lua_run_thread函數中,這個函數比較複雜,涉及的東西太多,稍後再細說。

如下面的代碼,沒有調用ngx.thread.wait去等待"light thread"的結束。

# [1] 沒有調用ngx.thread.wait去等待"light thread"的結束
location /thread002 {
    content_by_lua_block {
        local function f(name)
            ngx.log(ngx.ERR, "thread name: ", name, ", now start")
            ngx.sleep(4)
            ngx.log(ngx.ERR, "thread name: ", name, ", now end")
        end
        local t1 = ngx.thread.spawn(f, "first")
        local t2 = ngx.thread.spawn(f, "second")
        ngx.log(ngx.ERR, "main thread end")
    }
}      

由Nginx的日志中可以看到目前的請求一直延遲到t1,t2兩個"light thread"最後退出才會結束。 Nginx中日志的順序也可以看出父協程和兩個"light thread"的執行那個順序。

2017/07/21 09:45:07 [error] 115387#0: *26 [lua] content_by_lua(thread.conf:140):3: thread name: first, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
2017/07/21 09:45:07 [error] 115387#0: *26 [lua] content_by_lua(thread.conf:140):3: thread name: second, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
2017/07/21 09:45:07 [error] 115387#0: *26 [lua] content_by_lua(thread.conf:140):10: main thread end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
2017/07/21 09:45:11 [error] 115387#0: *26 [lua] content_by_lua(thread.conf:140):5: thread name: first, now end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
2017/07/21 09:45:11 [error] 115387#0: *26 [lua] content_by_lua(thread.conf:140):5: thread name: second, now end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"      
ngx_lua_API 詳解 (一)

而如果代碼中主動調用了ngx.exit()結束請求,那麼t1,t2兩個沒有列印出完全的資訊就被kill掉了。

# [2] 代碼中主動調用了ngx.exit()結束請求,那麼t1,t2兩個沒有列印出完全的資訊就被kill掉了
location /thread003 {
    content_by_lua_block {
        local function f(name)
            ngx.log(ngx.ERR, "thread name: ", name, ", now start")
            ngx.sleep(4)
            ngx.log(ngx.ERR, "thread name: ", name, ", now end")
        end
        local t1 = ngx.thread.spawn(f, "first")
        local t2 = ngx.thread.spawn(f, "second")
        ngx.exit(200)
    }
}      

相應的Nginx日志

2017/07/21 09:48:08 [error] 115508#0: *28 [lua] content_by_lua(thread.conf:156):3: thread name: first, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread003 HTTP/1.1", host: "127.0.0.1:8689"
2017/07/21 09:48:08 [error] 115508#0: *28 [lua] content_by_lua(thread.conf:156):3: thread name: second, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread003 HTTP/1.1", host: "127.0.0.1:8689"
2017/07/21 09:48:08 [error] 115508#0: *28 [lua] content_by_lua(thread.conf:156):10: main thread end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread003 HTTP/1.1", host: "127.0.0.1:8689"      

"light thread"的限制

2017/07/21 09:14:10 [error] 114598#0: *1 failed to load inlined Lua code: content_by_lua(thread.conf:128):2: ')' expected near ',', client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread001 HTTP/1.1", host: "127.0.0.1:8689"      

繼續閱讀