記錄使用openresty在web中的高級使用技術
openresty內部處理流程說明
nginx中這三個階段的執行順序:access--》content--》body_filter;
access_by_lua_file:獲取協議版本-->獲取bodys數據-->協議解碼-->設置body數據
content_by_lua_file:正常處理業務邏輯,零修改
body_filter_by_lua_file:判斷協議版本-->協議編碼
1、與其他location配合使用
內部調用:例如對數據庫、內部公共函數的統一接口,可以把它們放到統一的location中,通常情況下,爲了保護這些內部接口函數,都會把這些設置爲internal。可以讓內部接口相對獨立,不受外界干擾。
如圖 30行。interal設置爲內部location,直接訪問報404。
其中的函數介紹:
ngx.req.get_uri_args()函數爲回去請求url的參數。
ngx.location.capture("/sum",{args={a=10,b=33}})這個函數是lua內部來調用nginx的其他location函數,第二參數表示入參吧。
注意:
ngx.sleep(0.1) 休眠0.1毫秒
ngx.now() 獲取服務器當前時間;
ngx.location.capture_multi({},{}) : 表示並行執行兩個請求,當兩個請求沒有依賴關係,這種方式可以極大的提高查詢效率。可以被廣泛應用於廣告系統,高併發前端展示(並行無依賴界面,降級開關等) 如上圖 58行。
2、nginx內部跳轉
ngx.exec() 純粹是內部跳轉並且沒有引入任何額外的HTTP信號。
alias 與root 的區別:
因爲root屬性指定的值是要加入到最終路徑的,所以訪問的位置變成了/var/lib/www/website/
。而我不想把訪問的URI加入到路徑中。所以就需要使用alias屬性,其會拋棄URI,直接訪問alias指定的位置, 所以最終路徑變成/var/lib/www/
3、外部重定向
ngx.redirect():當請求http://10.23.148.149:9002/foo_rewrite會直接重定向到http://10.23.148.149:9002/foo,外部重定向是可以跨域名的。
4、獲取url的參數
ngx.req.get_uri_args、ngx.req.get_post_args一個獲取get請求的參數,一個是post請i去的參數
注意:在解析body post的參數之前, 一定要先讀取body,不然會報錯。
ngx.req.read_body():讀取body函數
5、傳遞請求uri的參數
當獲取到了參數,自然是需要這些參數來完成業務邏輯。URI內容傳遞過程中是需要調用ngx.encode_args進行規則轉義。
也可以不使用ngx.encode_args,但是寫法上比較醜。
注意:對於使用ngx.location.capture,args參數是可以接受字符串或者lua表的。
6、獲取請求body
在nnginx的典型應用場景中,幾乎都是讀取HTTP請求頭信息,例如負載均衡、反向代理等,對於API或者web 服務,獲取body信息在某些場景就是有必要的了。
未獲取到jack body 信息,注意:需要添加lua指令 lua_need_request_body on;默認讀取body信息。
添加後即可獲取到body信息。
大多數場景可以使用 ngx.req.read_body() 函數來獲取body 的post參數即可。不推薦使用lua_need_request_body指令。
7、簡易防火牆、黑白名單的配置
其中的 ngx.var.remote_addr 表示獲取nginx的內置綁定變量。
8、防止SQL注入問題。可以使用resty.mysql的lua腳本庫來建立與mysql的鏈接
9、使用resty.http庫資源,函數完成了連接池、http請求。
9、redis的使用以及redis連接池
這是一個標準的redis接口調用,如果代碼調用頻率不高,沒有任何問題,如果重度依賴redis,且有大量的創建連接---數據操作--關閉連接,這就發現代碼中很多的重複。
local function close_redis(red)
if not red then
return
end
local ok, err = red:close()
if not ok then
ngx.say("close redis error : ", err)
end
end
local redis = require("resty.redis")
--創建實例
local red = redis:new()
--設置超時(毫秒)
red:set_timeout(1000)
--建立連接
local ip = "127.0.0.1"
local port = 6660
local ok, err = red:connect(ip, port)
if not ok then
ngx.say("connect to redis error : ", err)
return close_redis(red)
end
--調用API進行處理
ok, err = red:set("msg", "hello world")
if not ok then
ngx.say("set msg error : ", err)
return close_redis(red)
end
--調用API獲取數據
local resp, err = red:get("msg")
if not resp then
ngx.say("get msg error : ", err)
return close_redis(red)
end
--得到的數據爲空處理
if resp == ngx.null then
resp = '' --比如默認值
end
ngx.say("msg : ", resp)
close_redis(red)
使用連接池來複用連接
建立TCP連接需要三次握手而釋放TCP連接需要四次握手,而這些往返時延僅需要一次,以後應該複用TCP連接,此時就可以考慮使用連接池,即連接池可以複用連接。
我們只需要將之前的close_redis函數改造爲如下即可:
local function close_redis(red)
if not red then
return
end
--釋放連接(連接池實現)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --連接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("set keepalive error : ", err)
end
end
即設置空閒連接超時時間防止連接一直佔用不釋放;設置連接池大小來複用連接。
此處假設調用red:set_keepalive(),連接池大小通過nginx.conf中http部分的如下指令定義:
#默認連接池大小,默認30
lua_socket_pool_size 30;
#默認超時時間,默認60s
lua_socket_keepalive_timeout 60s;
注意:
a、連接池是每Worker進程的,而不是每Server的;
b、當連接超過最大連接池大小時,會按照LRU算法回收空閒連接爲新連接使用;
c、連接池中的空閒連接出現異常時會自動被移除;
d、連接池是通過ip和port標識的,即相同的ip和port會使用同一個連接池(即使是不同類型的客戶端如Redis、Memcached);
e、連接池第一次set_keepalive時連接池大小就確定下了,不會再變更;
10、PipeLine壓縮請求數量
當我們有連續多個命令需要發送給redis時,如果m每個命令都以一個數據包發送給redis,將會降低服務端的併發能力。因爲每發送一個TCP報文,會存在網絡延遲及操作系統的處理延遲。大部分情況下,網絡延遲要遠大於CPU的處理延遲,所以網絡延遲會成功系統性能的瓶頸,使得併發上不去。pipeline的機制是將多個命令匯聚到一個請求中,可以極大的減少請求數量減少網絡延時。在實際應用場景中,正確使用pipeline對性能的提升十分明顯。
11、script壓縮複雜請求
從pipeline中,我們知道對於多個簡單的redis命令可以匯聚到一個請求中,提升服務端的併發能力。然後,在有些場景下,我們每次命令的輸入需要引用上一個命令的結果作爲入參,甚至還需要對第一個結果進行一些加工,再把加工結果當成第二個命令的輸入。pipeline難以處理這樣的場景。但是可以使用redis裏面的script來壓縮這些複雜的命令。
其核心思想是redism命令裏嵌入lua腳本,來實現一些複雜操作。其腳本相關命令有:
eval、evalsha、script exists、 script flush、 script kill 、script load
這樣就可以將兩個get請求放到一個TCP請求中,做到減少TCP請求數據,減少網絡延時。