高併發 Nginx+Lua OpenResty系列(3)——模塊指令

Nginx Lua 模塊指令

Nginx共11個處理階段,而相應的處理階段是能夠作插入式處理,便可插拔式架構;另外指令能夠在http、server、server if、location、location if幾個範圍進行配置:html

指令 所到處理階段 使用範圍 解釋
init_by_lua
init_by_lua_file
loading-config http nginx Master進程加載配置時執行;
一般用於初始化全局配置/預加載Lua模塊
init_worker_by_lua
init_worker_by_lua_file
starting-worker http 每一個Nginx Worker進程啓動時調用的計時器,若是Master進程不容許則只會在init_by_lua以後調用;
一般用於定時拉取配置/數據,或者後端服務的健康檢查
set_by_lua
set_by_lua_file
rewrite server,server
if,location,location if
設置nginx變量,能夠實現複雜的賦值邏輯;此處是阻塞的,Lua代碼要作到很是快;
rewrite_by_lua
rewrite_by_lua_file
rewrite
tail
http,server,location,location
if
rrewrite階段處理,能夠實現複雜的轉發/重定向邏輯;
access_by_lua
access_by_lua_file
access tail http,server,location,location
if
請求訪問階段處理,用於訪問控制
content_by_lua
content_by_lua_file
content location,location
if
內容處理器,接收請求處理並輸出響應
header_filter_by_lua
header_filter_by_lua_file
output-header-filter http,server,location,location
if
設置header和cookie
body_filter_by_lua
body_filter_by_lua_file
output-body-filter http,server,location,location
if
對響應數據進行過濾,好比截斷、替換。
log_by_lua
log_by_lua_file
log http,server,location,location
if
log階段處理,好比記錄訪問量/統計平均響應時間

更詳細的解釋請參考http://wiki.nginx.org/HttpLuaModule#Directives。如上指令不少並不經常使用,所以咱們只拿其中的一部分作演示。nginx

init_by_lua

每次Nginx從新加載配置時執行,能夠用它來完成一些耗時模塊的加載,或者初始化一些全局配置;在Master進程建立Worker進程時,此指令中加載的全局變量會進行Copy-OnWrite,即會複製到全部全局變量到Worker進程。web

1. nginx.conf配置文件中的http部分添加以下代碼

#共享全局變量,在全部worker間共享
lua_shared_dict shared_data 1m;

init_by_lua_file /usr/example/lua/init.lua;

2. init.lua

-- 初始化耗時的模塊
local redis = require("resty.redis")
local cjson = require("cjson")

-- 全局變量,不推薦
count = 1

-- 共享全局內存
local shared_data = ngx.shared.shared_data
shared_data:set("count", 1)

3. test.lua

count = count + 1
ngx.say("global variable:", count)
local shared_data = ngx.shared.shared_data
ngx.say(", shared memory : ", shared_data:get("count"))
shared_data:incr("count", 1)
ngx.say("hello world")

4. 檢驗結果

訪問如http://127.0.0.1/lua 會發現全局變量一直不變,而共享內存一直遞增
global variable : 2 , shared memory : 8 hello world
很不幸,上面只是理論結果,實際本人測試爲全局變量會隨着刷新而改動,永遠比共享內存大1,懷疑nginx新的版本更新了全局變量的定義


另外注意必定在生產環境開啓lua_code_cache,不然每一個請求都會建立Lua VM實例。redis

init_worker_by_lua

用於啓動一些定時任務,好比心跳檢查,定時拉取服務器配置等等;此處的任務是跟Worker進程數量有關係的,好比有2個Worker進程那麼就會啓動兩個徹底同樣的定時任務。json

1. nginx.conf配置文件中的http部分添加以下代碼

init_worker_by_lua /usr/openResty/lua/init_worker.lua;

2. init_worker.lua

local count = 0
local delayInSeconds = 3
local heartbeatCheck = nil
heartbeatCheck = function ( args )
  count = count + 1
  ngx.log(ngx.ERR, "do check ", count)

    local ok, err = ngx.timer.at(delayInSeconds, heartbeatCheck)

    if not ok then
      ngx.log(ngx.ERR, "failed to startup heartbeart worker...", err) 
    end
end

heartbeatCheck()

ngx.timer.at:延時調用相應的回調方法;ngx.timer.at(秒單位延時,回調函數,回調函數的參數列表);能夠將延時設置爲0即獲得一個當即執行的任務,任務不會在當前請求中執行不會阻塞當前請求,而是在一個輕量級線程中執行。
另外根據實際狀況設置以下指令
lua_max_pending_timers 1024;  #最大等待任務數
lua_max_running_timers 256;    #最大同時運行任務數後端

set_by_lua

設置nginx變量,咱們用的set指令即便配合if指令也很難實現負責的賦值邏輯;服務器

1.1 openResty.conf配置文件

location /lua_set_1 {
        default_type "text/html";
        set_by_lua_file $num /usr/openResty/lua/test_set_1.lua;
        echo $num;
    }

set_by_lua_file:語法set_by_lua_file $var lua_file arg1 arg2…; 在lua代碼中能夠實現全部複雜的邏輯,可是要執行速度很快,不要阻塞;cookie

1.2 test_set_1.lua

local uri_args = ngx.req.get_uri_args()
local i = uri_args["i"] or 0
local j = uri_args["j"] or 0
  
return i + j

獲得請求參數進行相加而後返回。
訪問如http://127.0.0.1/lua_set_1?i=1&j=10進行測試。 若是咱們用純set指令是沒法實現的。
再舉個實際例子,咱們實際工做時常常涉及到網站改版,有時候須要新老並存,或者切一部分流量到新版
2.1 在openResty.conf中使用map指令來映射host到指定nginx變量,方便咱們測試架構

############ 測試時使用的動態請求  
    map $host $item_dynamic {  
        default                     "0";
        item2014.jd.com            "1";
    }

如綁定hosts
192.168.1.2 item.jd.com;
192.168.1.2 item2014.jd.com;
此時咱們想訪問item2014.jd.com時訪問新版,那麼咱們能夠簡單的使用如svg

if ($item_dynamic = "1") {  
     proxy_pass http://new;
 }  
 proxy_pass http://old;

可是咱們想把商品編號爲爲8位(好比品類爲圖書的)沒有改版完成,須要按照相應規則跳轉到老版,可是其餘的到新版;雖然使用if指令能實現,可是比較麻煩,基本須要這樣

set jump "0";
if($item_dynamic = "1") {
    set $jump "1";
}
if(uri ~ "^/6[0-9]{7}.html") {
   set $jump "${jump}2";
}
#非強制訪問新版,且訪問指定範圍的商品
if (jump == "02") {
   proxy_pass http://old;
}
proxy_pass http://new;

以上規則仍是比較簡單的,若是涉及到更復雜的多重if/else或嵌套if/else實現起來就更痛苦了,可能須要到後端去作了;此時咱們就能夠藉助lua了:

set_by_lua $to_book '
     local ngx_match = ngx.re.match
     local var = ngx.var
     local skuId = var.skuId
     local r = var.item_dynamic ~= "1" and ngx.re.match(skuId, "^[0-9]{8}$")
     if r then return "1" else return "0" end;
';
set_by_lua $to_mvd '
     local ngx_match = ngx.re.match
     local var = ngx.var
     local skuId = var.skuId
     local r = var.item_dynamic ~= "1" and ngx.re.match(skuId, "^[0-9]{9}$")
     if r then return "1" else return "0" end;
';
#自營圖書
if ($to_book) {
    proxy_pass http://127.0.0.1/old_book/$skuId.html;
}  
#自營音像
if ($to_mvd) {
    proxy_pass http://127.0.0.1/old_mvd/$skuId.html;
}
#默認
proxy_pass http://127.0.0.1/proxy/$skuId.html;

rewrite_by_lua

執行內部URL重寫或者外部重定向,典型的如僞靜態化的URL重寫。其默認執行在rewrite處理階段的最後。

1.1 openResty.conf配置文件

location /lua_rewrite_1 {  
        default_type "text/html";  
        rewrite_by_lua_file /usr/openResty/lua/test_rewrite_1.lua;  
        echo "no rewrite";  
    }

1.2 test_rewrite_1.lua

if ngx.req.get_uri_args()["jump"] == "1" then
  return ngx.redirect("http://www.baidu.com?jump=1", 302)
end

當咱們請求http://127.0.0.1/lua_rewrite_1時發現沒有跳轉,而請求http://127.0.0.1/lua_rewrite_1?jump=1時發現跳轉到百度首頁了。 此處須要301/302跳轉根據本身需求定義。

2.1 openResty.conf配置文件

location /lua_rewrite_2 {
        default_type "text/html";
        rewrite_by_lua_file /usr/openResty/lua/test_rewrite_2.lua;
        echo "no rewrite";
    }

2.2 test_rewrite_2.lua

if ngx.req.get_uri_args()["jump"] == "1" then
  ngx.req.set_uri("/lua_rewrite_3", false);
  ngx.req.set_uri("/lua_rewrite_4", false);
  ngx.req.set_uri_args({a = 1, b = 2});
end

ngx.req.set_uri(uri, false):能夠內部重寫uri(能夠帶參數),等價於 rewrite ^ /lua_rewrite_3;經過配合if/else能夠實現 rewrite ^ /lua_rewrite_3 break;這種功能;此處二者都是location內部url重寫,不會從新發起新的location匹配;
ngx.req.set_uri_args:重寫請求參數,能夠是字符串(a=1&b=2)也能夠是table;


訪問如http://127.0.0.1/lua_rewrite_2?jump=0時獲得響應
rewrite2 uri : /lua_rewrite_2, a :


訪問如http://127.0.0.1/lua_rewrite_2?jump=1時獲得響應
rewrite2 uri : /lua_rewrite_4, a : 1

3.1 openResty.conf配置文件

location /lua_rewrite_3 {
        default_type "text/html";
        rewrite_by_lua_file /usr/openResty/lua/test_rewrite_3.lua;
        echo "no rewrite";
    }

3.3 test_rewrite_3.lua

if ngx.req.get_uri_args()["jump"] == "1" then
  ngx.req.set_uri("/lua_rewrite_4", true);
  ngx.log(ngx.ERR, "=========")
  ngx.req.set_uri_args({a = 1, b = 2});
end

ngx.req.set_uri(uri, true):能夠內部重寫uri,即會發起新的匹配location請求,等價於 rewrite ^ /lua_rewrite_4 last;此處看error log是看不到咱們記錄的log。
因此請求如http://127.0.0.1/lua_rewrite_3?jump=1會到新的location中獲得響應,此處沒有/lua_rewrite_4,因此匹配到/lua請求,獲得相似以下的響應
global variable : 2 , shared memory : 1 hello world

rewrite ^ /lua_rewrite_3;                 等價於  ngx.req.set_uri("/lua_rewrite_3", false);
rewrite ^ /lua_rewrite_3 break;       等價於  ngx.req.set_uri("/lua_rewrite_3", false); 加 if/else判斷/break/return
rewrite ^ /lua_rewrite_4 last;           等價於  ngx.req.set_uri("/lua_rewrite_4", true);
注意,在使用rewrite_by_lua時,開啓rewrite_log on;後也看不到相應的rewrite log。

access_by_lua

用於訪問控制,好比咱們只容許內網ip訪問,可使用以下形式

allow     127.0.0.1;  
allow     10.0.0.0/8;  
allow     192.168.0.0/16;  
allow     172.16.0.0/12;  
deny      all;

1. openResty.conf配置文件

location /lua_access {
        default_type "text/html";
        rewrite_by_lua_file /usr/openResty/lua/test_access.lua;
        echo "access";
    }

2. test_access.lua

if ngx.req.get_uri_args()["token"] ~= "123" then
  return ngx.exit(403)
end

即若是訪問如http://17.0.0.12/lua_access?token=234將獲得403 Forbidden的響應。這樣咱們能夠根據如cookie/用戶token來決定是否有訪問權限。


另外在使用PCRE進行正則匹配時須要注意正則的寫法,具體規則請參考http://wiki.nginx.org/HttpLuaModule中的Special PCRE Sequences部分。還有其餘的注意事項也請閱讀官方文檔。

相關文章
相關標籤/搜索