系統版本及需求:html
OS
:CentOS 7.7.1908nginx
OpenResty
:1.15.8.2git
lua-nginx-module模塊是什麼:github
It is a core component of OpenResty. If you are using this module, then you are essentially using OpenResty.web
By leveraging Nginx's subrequests, this module allows the integration of the powerful Lua threads (known as Lua "coroutines") into the Nginx event model.vim
Unlike Apache's mod_lua and Lighttpd's mod_magnet, Lua code executed using this module can be 100% non-blocking on network traffic as long as the Nginx API for Lua provided by this module is used to handle requests to upstream services such as MySQL, PostgreSQL, Memcached, Redis, or upstream HTTP web services.後端
OpenResty的核心組件,將lua線程集成到nginx模型中,且不會阻塞網絡流量。api
能夠經過編譯將其安裝爲Nginx Module。本文直接安裝OpenResty,經過lua腳本主要實現如下兩個目標:服務器
經過以上兩個目標,也很容易衍生出其餘的可能性,例如經過此模塊實現根據請求用戶的特徵將其調度到不一樣的服務器:以此來達到目標(好比灰度、就近訪問、黑白名單等);根據轉發多後端特性,實現徹底的真實環境壓力測試等。
經過源碼編譯安裝,具體步驟以下:
yum install -y pcre-devel openssl-devel gcc curl mkdir -p /data/pkg/ && cd /data/pkg/ wget https://openresty.org/download/openresty-1.15.8.2.tar.gz tar xf openresty-1.15.8.2.tar.gz cd openresty-1.15.8.2 ./configure --with-file-aio --with-http_ssl_module --with-http_realip_module --with-http_sub_module --with-http_gzip_static_module --with-http_auth_request_module --with-http_stub_status_module make -j$nproc make install
編譯時Nginx的大多數選項都支持。如上啓動了一些Module,具體根據你的需求選擇。
默認安裝路徑/usr/local/openresty
,想更改路徑經過--prefix=/path指定。
建立一個存放腳本的目錄:
cd /usr/local/openresty mkdir nginx/conf/lua
建立一個lua測試腳本:
vim nginx/conf/lua/hello.lua
local action = ngx.var.request_method if(action == "POST") then ngx.say("Method: POST; Hello world") elseif(action == "GET") then ngx.say("Method: GET; Welcome to the web site") end
在Server段配置中啓用lua腳本:
vim nginx/conf/nginx.conf
在nginx.conf中新增長一個server段,請求路徑以/開頭的則使用lua腳本進行處理。
server { listen 0.0.0.0:8080; location / { root html; index index.html index.htm; } location ~* ^/(.*)$ { content_by_lua_file "conf/lua/hello.lua"; # lua script location } }
測試效果:
使用./bin/openresty -t命令檢查配置無誤,而後使用./bin/openresty命令啓動服務。
POST和GET請求方式返回不一樣的相應內容
curl 127.0.0.1:8080 # 返回信息 Method: GET; Welcome to the web site curl -d "" 127.0.0.1:8080 # 返回信息 Method: POST; Hello world
環境準備穩當,如今經過lua腳本程序配合openresty實現對於HTTP請求的複製。
當一個請求來到openresty服務時,把此請求轉發給後端的server一、server2等等,但只是用server1或server2的應答消息回覆這個請求。
一個簡單的示例圖:
需求:將請求同時發送到/prod和/test路徑後的真實後端,但只使用/prod的後端回覆client的請求
lua代碼
vim nginx/conf/lua/copyRequest.lua
function req_copy() local resp_prod, resp_test = ngx.location.capture_multi { {"/prod" .. ngx.var.request_uri, arry}, {"/test" .. ngx.var.request_uri, arry}, } if resp_prod.status == ngx.HTTP_OK then local header_list = {"Content-Type", "Content-Encoding", "Accept-Ranges"} for _, i in ipairs(header_list) do if resp_prod.header[i] then ngx.header[i] = resp_prod.header[i] end end ngx.say(resp_prod.body) else ngx.say("Upstream server error : " .. resp_prod.status) end end req_copy()
nginx新建的server段配置以下,且引入vhosts目錄下配置文件,nginx/conf/nginx.conf:
server { listen 0.0.0.0:8080; location / { root html; index index.html index.htm; } # 匹配lua文件中/prod+原請求的uri(/copy/*) location ^~ /prod/ { # 路徑重寫後,/prod後端服務器收到的路徑爲客戶端請求路徑去掉開頭/copy/ rewrite /prod/copy/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8081; } # 匹配lua文件中/test+原請求的uri(/copy/*) location ^~ /test/ { # 路徑重寫後,/prod後端服務器收到的路徑爲客戶端請求路徑去掉開頭/copy/ rewrite /test/copy/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8082; } location ^~ /copy/ { content_by_lua_file "conf/lua/copyRequest.lua"; } } include vhosts/*.conf;
建立兩個後端主機,模擬表明不一樣環境:
# nginx/conf/vhosts/prod.conf server { listen 8081; server_name localhost; access_log logs/prod_server.log; location / { return 200 "Welcome to prod server"; } location /api/v1 { return 200 "API V1"; } } # nginx/conf/vhosts/test.conf server { listen 8082; server_name localhost; access_log logs/test_server.log; location / { return 200 "Welcome to test server"; } }
配置更新後重載服務,而後測試訪問:
> curl 127.0.0.1:8080/copy/ Welcome to prod server > curl 127.0.0.1:8080/copy/api/v1 API V1 # /prod和/test後端的服務都收到了請求 # 查看日誌;tail -2 nginx/logs/prod_server.log 127.0.0.1 - - [01/Mar/2020:01:41:12 +0800] "GET / HTTP/1.0" 200 22 "-" "curl/7.29.0" 127.0.0.1 - - [01/Mar/2020:01:41:15 +0800] "GET /api/v1 HTTP/1.0" 200 6 "-" "curl/7.29.0" # 查看日誌;tail -2 nginx/logs/test_server.log 127.0.0.1 - - [01/Mar/2020:01:41:12 +0800] "GET / HTTP/1.0" 200 22 "-" "curl/7.29.0" 127.0.0.1 - - [01/Mar/2020:01:41:15 +0800] "GET /api/v1 HTTP/1.0" 200 22 "-" "curl/7.29.0"
模擬場景圖示:
IP:8080/copy/
路徑/prod和/test
路徑/prod和/test
路徑在本地被代理到真實後端/prod(resp_prod)
後端服務器返回的內容(見lua腳本中代碼)需求:將請求報文參數中的userid在某個區間的請求調度到指定的後端服務上。
建立nginx/conf/lua/requestBody.lua代碼:
-- post body提交方式爲application/x-www-form-urlencoded的內容獲取方法 function urlencodedMethod() local postBody = {} for key, val in pairs(args) do postBody[key] = val end local uid = postBody["userid"] postBody = nil return tonumber(uid) end -- get請求方式爲xx.com/?userid=x其params的獲取方式 function uriParameterMethod() local getParameter = {}, key, val for key, val in pairs(args) do if type(val) == "table" then getParameter[key] = table.concat(val) else getParameter[key] = val end end local uid = getParameter["userid"] getParameter = nil return tonumber(uid) end -- 獲取post body提交的方式;multipart/from-data在此示例中沒有實現對其內容的處理 function contentType() local conType = ngx.req.get_headers()["Content-Type"] local conTypeTable = {"application/x-www-form-urlencoded", "multipart/form-data"} local receiveConType, y if(type(conType) == "string") then for y = 1, 2 do local word = conTypeTable[y] local from, to, err = ngx.re.find(conType, word, "jo") if from and to then receiveConType = string.sub(conType, from, to) end end else receiveConType = nil end return receiveConType end -- 循環出一些須要的header返回給客戶端 function iterHeaders(resp_content) local header_list = {"Content-Type", "Content-Encoding", "Accept-Ranges","Access-Control-Allow-Origin", "Access-Control-Allow-Methods","Access-Control-Allow-Headers", "Access-Control-Allow-Credentials"} for _, i in ipairs(header_list) do if(resp_content.header[i]) then ngx.header[i] = resp_content.header[i] end end return resp_content end -- 將userid大於等於1小於等於10的請求發送給/prod路徑的後端 -- 將userid大於等於11小於等於20的請求發送給/test路徑的後端 -- 將userid非以上兩種的同時發送給/prod和/test路徑的後端,使用/prod後端回覆請求 function requestTo(uid) local resp, resp_noReply if(uid >= 1 and uid <= 10) then resp = ngx.location.capture_multi { {"/prod".. ngx.var.request_uri, arry}, } elseif(uid >= 11 and uid <= 20) then resp = ngx.location.capture_multi { {"/test".. ngx.var.request_uri, arry}, } else resp, resp_noReply = ngx.location.capture_multi { {"/prod" .. ngx.var.request_uri, arry}, {"/test" .. ngx.var.request_uri, arry}, } end local res if(resp.status == ngx.HTTP_OK) then res = iterHeaders(resp) else res = "Upstream server err : " .. reps_content.status end ngx.say(res.body) end -- 處理主函數 function main_func() ngx.req.read_body() local action = ngx.var.request_method if(action == "POST") then arry = {method = ngx.HTTP_POST, body = ngx.req.read_body()} args = ngx.req.get_post_args() elseif(action == "GET") then args = ngx.req.get_uri_args() arry = {method = ngx.HTTP_GET} end local u if(action == "POST") then if args then local getContentType = contentType() if(getContentType == "application/x-www-form-urlencoded") then u = urlencodedMethod() end end elseif(action == "GET") then if args then u = uriParameterMethod() end end if(u == nil) then ngx.say("Request parameter cannot be empty: userid<type: int>") else requestTo(u) end end main_func()
配置nginx/conf/nginx.conf文件,修改新增的server段內容以下:
server { listen 0.0.0.0:8080; location / { root html; index index.html index.htm; } location ^~ /prod/ { rewrite /prod/request/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8081; } location ^~ /test/ { rewrite /test/request/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8082; } location ^~ /request/ { content_by_lua_file "conf/lua/requestBody.lua"; } } include vhosts/*.conf;
使用上個示例中的兩個後端服務,重載服務,測試效果:
> curl -X POST -d 'userid=1' 127.0.0.1:8080/request/ Welcome to prod server > curl -X POST -d 'userid=11' 127.0.0.1:8080/request/ Welcome to test server > curl -X POST -d 'userid=21' 127.0.0.1:8080/request/ Welcome to prod server > curl 127.0.0.1:8080/request/?userid=1 Welcome to prod server > curl 127.0.0.1:8080/request/?userid=11 Welcome to test server > curl 127.0.0.1:8080/request/?userid=21 Welcome to prod server
請求過程解析:
IP:8080/copy/
路徑tips:
經過使用lua-nginx-module擴展加強nginx處理能力,能夠根據自身的業務需求開發腳本,實現針對請求的方式、參數等內容進行按需調度。