導讀:nginx是一個高性能的反向代理服務器,lua是一個小巧的腳本語言,這兩個的巧妙結合會擦出怎樣的火花呢。
關鍵詞:nginx,lua,nginx+lua
前言
nginx,lua,nginx+lua,這三個名詞不知道大家熟悉多少。為了后面內容的展示,我簡單的介紹一下它們,想深入了解的網上資料很多,在這就不啰嗦了。nginx是一個高性能的反向代理服務器,一般會處在網站的最前端(有可能前面還會加一層slb,在這暫時忽略),用來做后端web服務的代理;lua是一個小巧的腳本語言,其設計的目的就是嵌入應用程序中,為其提供一些擴展和增強,比如redis,nginx等等;nginx+lua顧名思義就是nginx和lua的結合體,這兩者之間溝通的橋梁是nginx_lua_module,它是nginx的一個模塊,有了它nginx和lua才能互通,筆者在最近幾年的工作中恰好有這方面的開發經驗,所以想把這些真實的使用場景分享出來供大家做參考,具體的api doc還請大家參考官方資料。
案例1-入口層流量的灰度識別
何謂入口層流量的灰度識別呢,簡單來說就是A用戶的請求打到線上環境,B用戶的請求打到灰度環境,目的就是做新功能的驗證,實現邏輯很簡單,大體流程如下:
1.測試同學在灰度控制台配置灰度規則,規則里會約束哪些url下哪些商戶的請求進入灰度環境;
2.灰度控制台推送規則給入口層nginx,nginx會將規則存儲到本地內存中,借助ngx.shared.DICT;
3.請求進入的時候(通過rewrite_by_lua_file觸發)獲取本地內存中的規則進行比對,如何命中規則就將請求轉發到灰度環境,對nginx來說就是切換不同的upstream,比如線上是prod_serverA,灰度是gray_serverA;
代碼片段如下:
upstream gray_serverA { server 192.68.1.1:8080; } upstream prod_serverA { server 192.68.1.2:8080; } server { listen 80; server_name graytest.demo.com; charset utf-8; location ~ \.do$ { set $backend 'prod_serverA'; #默認的upstream為線上服務 rewrite_by_lua_file "conf/lua-gray/rewriter.lua"; # rewrite_by_lua_file 可以簡單的理解為一個過濾器,nginx在rewrite階段會執行你指定的腳本文件, #在這個文件中我們會判斷請求是否為灰度請求如果是灰度請求就將backend改為gray_serverA proxy_pass http://$backend; } }
案例2-入口層記錄錯誤日志
之前有同學反饋說springmvc偶爾會報415錯誤,這個錯誤的一個常見原因是傳遞的Content-Type頭不對,比如后端需要application/json,但是前端傳遞了application/x-www-form-urlencoded,那就會報這個錯。但是跟前端確認了傳遞的頭是沒有問題的,有人猜測可能是頭信息有特殊字符,導致后端web 容器(tomcat、resin)解析頭出現了問題,既然這樣把所有請求頭打出來一看究竟,於是lua再一次出場,這次主要用來輸出請求頭到日志文件中,主要用到了log_by_lua_block這個指令,代碼片段如下:
location ~ \.do$ { proxy_pass http://$backend; log_by_lua_block {
//判斷下如果http 響應狀態碼為415就輸出請求頭到文件中 if tonumber(ngx.var.status) == 415 then ngx.log(ngx.ERR,"upstream reponse status is 415,please notice it,here are request headers:") local h, err = ngx.req.get_headers(100,true) if err == "truncated" then ngx.log(ngx.ERR,"request headers beyond 100,will not return") else local cjson = require("cjson") ngx.log(ngx.ERR,cjson.encode(h)) end end } }
想詳細了解這個案例的,請移步到我的另一篇博客(https://www.cnblogs.com/chopper-poet/p/11625144.html)。
案例3-將nginx信息注冊到監控平台
需求很簡單,當nginx啟動的時候將自身信息上報到redis中,上報內容包扣自身的ip,代理的域名信息等,有個監控平台會定期從redis讀取這些信息做展示,方便運維干活,流程很簡單就是定時讀取自身ip和代理的域名信息寫到redis中,為什么要定時呢,這里還起到一種心跳的效果,當長時間沒有上報時可能是nginx出問題了,主要用到的指令如下:
1.init_worker_by_lua_file "conf/ua-grayngxReloadListener.lua",當nginx啟動或者reload的時候會執行指定的文件;
2.ngx.worker.id() 這個方法會返回nginx worker進程的編號,從0開始,如果nginx有4個worker,那取值范圍為0-3,這個主要是為了防止並發上報,因為第一步里面提到的init_worker_by_lua_file,如果有幾個worker就會觸發幾次,所以為了只上報一次,會判斷下返回值是否為0,也就是只讓第一個worker執行;
3.ngx.timer.every 用來執行定時任務;
代碼片段如下:
local workerId = ngx.worker.id() if(workerId == 0) then ngx.log(ngx.INFO,'workerId is 0 will startup task') local ok, err = ngx.timer.every(4,function() #1. get local ip and domins #2. write to redis end) else ngx.log(ngx.INFO,'workerId is not 0 ,just ignore it') end
案例4-將入口層流量同時轉發到多個后端服務
類似於消息隊列的發布訂閱一樣,在nginx這一層可以將一個請求同時發到多個地址,代碼片段如下:
location ~ /capture_test$ { content_by_lua_block { ngx.req.read_body() res1, res3 = ngx.location.capture_multi{ { "/capture_test1",{ method = ngx.HTTP_POST, always_forward_body=true}}, { "/capture_test2",{ method = ngx.HTTP_POST, always_forward_body=true}}, } ngx.say(res3.body) ngx.exit(res3.status) }
總結
上面列舉了nginx+lua在我司使用的四個案例,真實場景要復雜很多,為了方便大家理解,特意將案例做了簡化。如果覺得有用,請點個推薦。