ngx_lua中的協程調度(四)

ngx_lua中訪問多個第三方服務

ngx_lua中提供了ngx.socket API,能夠方便的訪問第三方網絡服務。以下面的代碼,經過get_response函數從兩個(或者更多)的源服務器獲取數據,再生成響應發給客戶端。nginx

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就是用來解決這種問題的。git

ngx.thread API

lua-nginx-module提供了三個APIgithub

  • ngx.thread.spawn
  • ngx.thread.wait
  • ngx.thread.kill

關於API的介紹能夠參考官方文檔shell

經過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重寫上面的代碼socket

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"能夠同時運行,總的耗時只至關於訪問一個源服務器的時間,即便須要訪問的源服務器增長,耗時沒有太大的變化。tcp

"light thread"的調度

Linux中的fork生成新的子進程,父進程與子進程誰先運行呢?都有可能,和系統的調度有關。函數

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

"light thread"的限制

"light thread"畢竟是基於依附於請求的,如在content_by_lua中建立的"light thread",是徹底與當前的請求關聯的,若是"light thread"沒有退出,當前請求也沒法結束。一樣若是當前請求由於錯誤退出,或調用ngx.exit強制退出時,處於運行狀態的"light thread"也會被kill掉。不像操做系統的進程,父進程退出後,子進程能夠被init進程"收養"。

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

location / {
            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/03/02 22:43:21 [error] 2142#0: *1 [lua] content_by_lua(nginx.conf:55):3: thread name: first, now start, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:43:21 [error] 2142#0: *1 [lua] content_by_lua(nginx.conf:55):3: thread name: second, now start, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:43:21 [error] 2142#0: *1 [lua] content_by_lua(nginx.conf:55):10: main thread end, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:43:25 [error] 2142#0: *1 [lua] content_by_lua(nginx.conf:55):5: thread name: first, now end, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:43:25 [error] 2142#0: *1 [lua] content_by_lua(nginx.conf:55):5: thread name: second, now end, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"

而若是代碼中主動調用了ngx.exit()結束請求,那麼t1,t2兩個沒有打印出徹底的信息就被kill掉了。

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)
        ngx.exit(100)
    }
}

相應的Nginx日誌

2017/03/02 22:48:01 [error] 2227#0: *1 [lua] content_by_lua(nginx.conf:56):3: thread name: first, now start, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:48:01 [error] 2227#0: *1 [lua] content_by_lua(nginx.conf:56):3: thread name: second, now start, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:48:01 [error] 2227#0: *1 [lua] content_by_lua(nginx.conf:56):10: main thread end, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:48:35 [error] 2227#0: *2 [lua] content_by_lua(nginx.conf:56):3: thread name: first, now start, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:48:35 [error] 2227#0: *2 [lua] content_by_lua(nginx.conf:56):3: thread name: second, now start, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
2017/03/02 22:48:35 [error] 2227#0: *2 [lua] content_by_lua(nginx.conf:56):10: main thread end, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
相關文章
相關標籤/搜索