個人博客: https://www.luozhiyun.com/archives/217nginx
想要學好 OpenResty,你必須理解下面 8 個重點:git
你不該該使用任何 Lua 世界的庫來解決上述問題,而是應該使用 cosocket 的 lua-resty-* 庫。Lua 世界的庫極可能會帶來阻塞,讓本來高性能的服務,直接降低幾個數量級。github
和nginx同樣,都有階段的概念,而且每一個階段都有本身不一樣的做用:編程
OpenResty 的 API 是有階段使用限制的。每個 API 都有一個與之對應的使用階段列表,若是你超範圍使用就會報錯。後端
具體的API能夠查閱文檔:https://github.com/openresty/lua-nginx-moduleapi
有些狀況下,咱們須要的是跨越階段的、能夠讀寫的變量。緩存
OpenResty 提供了 ngx.ctx,來解決這類問題。它是一個 Lua table,能夠用來存儲基於請求的 Lua 數據,且生存週期與當前請求相同。咱們來看下官方文檔中的這個示例:性能優化
location /test { rewrite_by_lua_block { ngx.ctx.foo = 76 } access_by_lua_block { ngx.ctx.foo = ngx.ctx.foo + 3 } content_by_lua_block { ngx.say(ngx.ctx.foo) } }
最終輸出79網絡
OPM(OpenResty Package Manager)是 OpenResty 自帶的包管理器
opm search lua-resty-http
session
不一樣於 OPM 裏只包含 OpenResty 相關的包,LuaRocks 裏面還包含 Lua 世界的庫。
luarocks search lua-resty-http
咱們還能夠去網站上看包的詳細信息:https://luarocks.org/modules/pintsized/lua-resty-http,這裏麪包含了做者、License、GitHub 地址、下載次數、功能簡介、歷史版本、依賴等。
awesome-resty 這個項目,就維護了幾乎全部 OpenResty 可用的包,而且都分門別類地整理好了。
由於nginx是多進程的程序:
因此信號分爲Master進程信號和worker進程信號。
Master進程:
worker進程:與master進程命令一一對應
Nginx命令行,至關於直接向master進程發送命令
mkdir geektime cd luoluo mkdir logs/ conf/
events { worker_connections 1024; } http { server { listen 8080; location / { content_by_lua ' ngx.say("hello, world") '; } } }
openresty -p `pwd` -c conf/nginx.conf 指定運行目錄:-p 使用指定的配置文件: -c
openresty後面跟隨的命令和nginx是同樣的
$ mkdir lua $ cat lua/hello.lua ngx.say("hello, world")
pid logs/nginx.pid; events { worker_connections 1024; } http { server { listen 8080; location / { content_by_lua_file lua/hello.lua; } } } }
這裏把 content_by_lua_block 改成 content_by_lua_file
$ sudo kill -HUP `cat logs/nginx.pid`
我這裏使用了發送信號的方式 -HUP表示重載配置文件
NYI,全稱爲 Not Yet Implemented。LuaJIT 中 JIT 編譯器的實現還不完善,有一些原語它還沒法編譯,由於這些原語實現起來比較困難,再加上 LuaJIT 的做者目前處於半退休狀態。這些原語包括常見的 pairs() 函數、unpack() 函數、基於 Lua CFunction 實現的 Lua C 模塊等。這樣一來,當 JIT 編譯器在當前代碼路徑上遇到它不支持的操做時,便會退回到解釋器模式。這些不能編譯的函數稱爲NYI。
NYI函數都在:http://wiki.luajit.org/NYI
在開發中,能夠先去找OpenResty的API:https://github.com/openresty/lua-nginx-module
例如,NYI 列表中 string 庫的幾個函數:
其中,string.byte 對應的可否被編譯的狀態是 yes,代表能夠被 JIT。
string.char 對應的編譯狀態是 2.1,代表從 LuaJIT 2.1 開始支持。咱們知道,OpenResty 中的 LuaJIT 是基於 LuaJIT 2.1 的,因此你也能夠放心使用。
string.dump 對應的編譯狀態是 never,即不會被 JIT,會退回到解釋器模式。
string.find 對應的編譯狀態是 2.1 partial,意思是從 LuaJIT 2.1 開始部分支持,後面的備註中寫的是 只支持搜索固定的字符串,不支持模式匹配。
LuaJIT 自帶的 jit.dump 和 jit.v 模塊。它們均可以打印出 JIT 編譯器工做的過程。前者會輸出很是詳細的信息,能夠用來調試 LuaJIT 自己;後者的輸出比較簡單,每行對應一個 trace,一般用來檢測是否能夠被 JIT。
使用resty:
$resty -j v -e
其中,resty 的 -j 就是和 LuaJIT 相關的選項;後面的值爲 dump 和 v,就對應着開啓 jit.dump 和 jit.v 模式。
以下例子:
$resty -j v -e 'local t = {} for i=1,100 do t[i] = i end for i=1, 1000 do for j=1,1000 do for k,v in pairs(t) do -- end end end'
上面的pairs是NYI的語句,不能被JIT,因此結果裏面就會顯示:
[TRACE 1 (command line -e):2 loop] [TRACE --- (command line -e):7 -- NYI: bytecode 72 at (command line -e):8]
shared dict(共享字典)是基於 NGINX 共享內存區的 Lua 字典對象,它能夠跨多個 worker 來存取數據,通常用來存放限流、限速、緩存等數據。
例子:
http { lua_shared_dict dogs 10m; server { location /demo { content_by_lua_block { local dogs = ngx.shared.dogs dogs:set("Jim", 8) local v = dogs:get("Jim") ngx.say(v) } } } }
簡單說明一下,在 Lua 代碼中使用 shared dict 以前,咱們須要在 nginx.conf 中用 lua_shared_dict 指令增長一塊內存空間,它的名字是 dogs,大小爲 10M。
也可使用resty CLI:
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs dogs:set("Jim", 8) local v = dogs:get("Jim") ngx.say(v)'
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
能夠看出, init 和 init_worker 兩個階段不在其中,也就是說,共享內存的 get API 不能在這兩個階段使用。
value, flags = ngx.shared.DICT:get(key)
正常狀況下:
第一個參數value 返回的是字典中 key 對應的值;但當 key 不存在或者過時時,value 的值爲 nil。
第二個參數 flags 就稍微複雜一些了,若是 set 接口設置了 flags,就返回,不然不返回。
一旦 API 調用出錯,value 返回 nil,flags 返回具體的錯誤信息。
cosocket 是把協程和網絡套接字的英文拼在一塊兒造成的,即 cosocket = coroutine + socket。
遇到網絡 I/O 時,它會交出控制權(yield),把網絡事件註冊到 Nginx 監聽列表中,並把權限交給 Nginx;當有 Nginx 事件達到觸發條件時,便喚醒對應的協程繼續處理(resume),最終實現了非阻塞網絡 I/O。
上下文:
rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*_
cosocket API 在 set_by_lua, log_by_lua, header_filter_by_lua* 和 body_filter_by_lua* 中是沒法使用的。init_by_lua* 和 init_worker_by_lua* 中暫時也不能用。
與這些API相應的Nginx指令:
$ resty -e 'local sock = ngx.socket.tcp() sock:settimeout(1000) -- one second timeout local ok, err = sock:connect("www.baidu.com", 80) if not ok then ngx.say("failed to connect: ", err) return end local req_data = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n" local bytes, err = sock:send(req_data) if err then ngx.say("failed to send: ", err) return end local data, err, partial = sock:receive() if err then ngx.say("failed to receive: ", err) return end sock:close() ngx.say("response is: ", data)'
在上面settimeout() ,做用是把鏈接、發送和讀取超時時間統一設置爲一個值。若是要想分開設置,就須要使用 settimeouts() 函數:
sock:settimeouts(1000, 2000, 3000)
receive 接收指定大小:
local data, err, partial = sock:receiveany(10240)
這段代碼就表示,最多隻接收 10K 的數據。
關於 receive,還有另外一個很常見的用戶需求,那就是一直獲取數據,直到遇到指定字符串才中止。
ocal reader = sock:receiveuntil("\r\n") while true do local data, err, partial = reader(4) if not data then if err then ngx.say("failed to read the data stream: ", err) break end ngx.say("read done") break end ngx.say("read chunk: [", data, "]") end
這段代碼中的 receiveuntil 會返回 \r\n 以前的數據,並經過迭代器每次讀取其中的 4 個字節。
沒有鏈接池的話,每次請求進來都要新建一個鏈接,就會致使 cosocket 對象被頻繁地建立和銷燬,形成沒必要要的性能損耗。
在你使用完一個 cosocket 後,能夠調用 setkeepalive() 放到鏈接池中:
local ok, err = sock:setkeepalive(2 * 1000, 100) if not ok then ngx.say("failed to set reusable: ", err) end
這段代碼設置了鏈接的空閒時間爲 2 秒,鏈接池的大小爲 100。在調用 connect() 函數時,就會優先從鏈接池中獲取 cosocket 對象。
需注意:
OpenResty 的定時任務能夠分爲下面兩種:
以下:
init_worker_by_lua_block { local function handler() local sock = ngx.socket.tcp() local ok, err = sock:connect(「www.baidu.com", 80) end local ok, err = ngx.timer.at(0, handler) }
啓動了一個延時爲 0 的定時任務。它啓動了回調函數 handler,並在這個函數中,用 cosocket 去訪問一個網站