摘要:通過lua-nginx-module中的ngx.thread同時執行多個任務。
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.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"的限制
"light thread"畢竟是基於依附於請求的,如在content_by_lua中創建的"light thread",是完全與當前的請求關聯的,如果"light thread"沒有退出,當前請求也無法結束。同樣如果當前請求因為錯誤退出,或調用ngx.exit強制退出時,處於運行狀態的"light thread"也會被kill掉。不像操作系統的進程,父進程退出后,子進程可以被init進程"收養"。
錯誤代碼:
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"