10 Nginx 擴展模塊Lua實踐


10 Nginx 擴展模塊Lua實踐

10.1 ngx_lua模塊概念

淘寶開發的ngx_lua模塊通過將lua解釋器集成進Nginx,可以采用lua腳本實現業務邏輯,由於lua的緊湊、快速以及內建協程,所以在保證高並發服務能力的同時極大地降低了業務邏輯實現成本。

10.2 ngx_lua模塊OpenRestry環境准備

(1)概述

前面我們提到過,OpenResty是由淘寶工程師開發的,所以其官方網站(http://openresty.org/)我們讀起來是非常的方便。OpenResty是一個基於Nginx與 Lua 的高性能 Web 平台,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。

用於方便地搭建能夠處理超高並發、擴展性極高的動態 Web 應用、Web 服務和動態網關。所以本身OpenResty內部就已經集成了Nginx和Lua,所以我們使用起來會更加方便。

(2)安裝

1)下載OpenResty

 wget https://openresty.org/download/openresty-1.19.3.2.tar.gz

2)解壓縮:

tar -zxvf openresty-1.19.3.2.tar.gz && cd openresty-1.19.3.2

3)編譯安裝

./configure && make && make install

4)進入OpenResty的目錄,找到nginx

cd /usr/local/openresty/nginx/

5)在conf目錄下的nginx.conf添加如下內容

location /lua{
  default_type 'text/html';
  content_by_lua 'ngx.say("<h1>HELLO,OpenRestry</h1>")';
}

6)在sbin目錄下啟動nginx

image-20211208173922205

7)通過瀏覽器訪問測試

http://192.168.5.3/lua

image-20211208171034015

image-20211208171052485

10.3 ngx_lua 命令介紹

使用Lua編寫Nginx腳本的基本構建塊是指令。指令用於指定何時運行用戶Lua代碼以及如何使用結果。下圖顯示了執行指令的順序。

1604717983815

先來解釋下*的作用

  • *:無 , 即 xxx_by_lua ,指令后面跟的是 lua指令

  • *:_file,即 xxx_by_lua_file 指令后面跟的是 lua文件

  • *:_block,即 xxx_by_lua_block 在0.9.17版后替換init_by_lua_file

(1)init_by_lua*

該指令在每次Nginx重新加載配置時執行,可以用來完成一些耗時模塊的加載,或者初始化一些全局配置。

(2)init_worker_by_lua*

該指令用於啟動一些定時任務,如心跳檢查、定時拉取服務器配置等。

(3)set_by_lua*

該指令只要用來做變量賦值,這個指令一次只能返回一個值,並將結果賦值給Nginx中指定的變量。

(4)rewrite_by_lua*

該指令用於執行內部URL重寫或者外部重定向,典型的如偽靜態化URL重寫,本階段在rewrite處理階段的最后默認執行。

(5)access_by_lua*

該指令用於訪問控制。例如,如果只允許內網IP訪問。

(6)content_by_lua*

該指令是應用最多的指令,大部分任務是在這個階段完成的,其他的過程往往為這個階段准備數據,正式處理基本都在本階段。

(7)header_filter_by_lua*

該指令用於設置應答消息的頭部信息。

(8)body_filter_by_lua*

該指令是對響應數據進行過濾,如截斷、替換。

(9)log_by_lua*

該指令用於在log請求處理階段,用Lua代碼處理日志,但並不替換原有log處理。

(10)balancer_by_lua*

該指令主要的作用是用來實現上游服務器的負載均衡器算法

(11)ssl_certificate_by_*

該指令作用在Nginx和下游服務開始一個SSL握手操作時將允許本配置項的Lua代碼。

10.4 ngx_lua 實操演示

10.4.1 基礎傳參顯示

http://192.168.5.3/getByGender?name=張三&gender=1

Nginx接收到請求后,根據gender傳入的值,如果gender傳入的是1,則在頁面上展示 張三先生,如果gender傳入的是0,則在頁面上展示張三女士,如果未傳或者傳入的不是1和2則在頁面上展示張三。

實現代碼

location /getByGender {
default_type 'text/html';
set_by_lua $name "
   -- 獲取請求URL上的參數對應值name gender
local uri_args = ngx.req.get_uri_args()
gender = uri_args['gender']
name = uri_args['name']
-- 條件判斷 if gender 1是先生 0是女士
if gender=='1' then
return name..'先生'
elseif gender=='0' then
return name..'女士'
else
return name
end
";
   charset utf-8;
return 200 $name;
}

image-20211209095223663

10.4.2 ngx_lua 操作 Redis

Redis在系統中經常作為數據緩存、內存數據庫使用,在大型系統中扮演着非常重要的作用。在Nginx核心系統中,Redis是常備組件。Nginx支持3種方法訪問Redis,分別是HttpRedis模塊、HttpRedis2Module、lua-resty-redis庫。

這三種方式中HttpRedis模塊提供的指令少,功能單一,適合做簡單緩存,HttpRedis2Module模塊比HttpRedis模塊操作更靈活,功能更強大。而Lua-resty-redis庫是OpenResty提供的一個操作Redis的接口庫,可根據自己的業務情況做一些邏輯處理,適合做復雜的業務邏輯。

(1)准備一個Redis環境

連接地址
host= 192.168.5.4
port=6379

image-20211209100105726

(2)准備對應的API

lua-resty-redis提供了訪問Redis的詳細API,包括創建對接、連接、操作、數據處理等。這些API基本上與Redis的操作一一對應。 1)redis = require "resty.redis"

2)new

語法: redis,err = redis:new(),創建一個Redis對象。

3)connect

語法:ok,err=redis:connect(host,port[,options_table]),設置連接Redis的連接信息。

  • ok:連接成功返回 1,連接失敗返回nil

  • err:返回對應的錯誤信息

4)set_timeout

語法: redis:set_timeout(time) ,設置請求操作Redis的超時時間。

5)close

語法: ok,err = redis:close(),關閉當前連接,成功返回1,失敗返回nil和錯誤信息 6)redis命令對應的方法

在lua-resty-redis中,所有的Redis命令都有自己的方法,方法名字和命令名字相同,只是全部為小寫。

(3)效果實現

location /Redis {
default_type "text/html";
content_by_lua_block{
-- 引入 Redis 對應的接口對象
local redis = require "resty.redis"
-- 創建一個 Redis 對象
local redisObj = redis:new()
-- 設置超時數據為 1s
redisObj:set_timeout(1000)
-- 設置 redis 連接信息
local ok,err = redisObj:connect("192.168.200.1",6379)
-- 判斷是否連接成功
if not ok then
ngx.say("failed to connection redis",err)
return
end
-- 向 Redis 中存入數據
ok,err = redisObj:set("username","TOM")
-- 判斷是否存入成功
if not ok then
ngx.say("failed to set username",err)
return
end
-- 從 redis 中獲取數據
local res,err = redisObj:get("username")
-- 將數據寫會消息體中
ngx.say(res)
-- 關閉連接
redisObj:close()
}
}

(4)運行測試效果

image-20211209101300744

image-20211209101220878

10.4.3 ngx_lua 操作 Mysql

MySQL是一個使用廣泛的關系型數據庫。在ngx_lua中,MySQL有兩種訪問模式,分別是使

  • 用ngx_lua模塊和lua-resty-mysql模塊:這兩個模塊是安裝OpenResty時默認安裝的。

  • 使用drizzle_nginx_module(HttpDrizzleModule)模塊:需要單獨安裝,這個庫現不在OpenResty中。

lua-resty-mysql是OpenResty開發的模塊,使用靈活、功能強大,適合復雜的業務場景,同時支持存儲過程的訪問。

使用lua-resty-mysql實現數據庫的查詢

(1)准備MYSQL

host: 192.168.5.4
port: 3306
username:root
password:123456

創建一個數據庫表及表中的數據。

create database nginx_db;

use nginx_db;

create table users(
id int primary key auto_increment,
username varchar(30),
birthday date,
salary double
);

insert into users(id,username,birthday,salary) values(null,"TOM","1988-11-11",10000.0);
insert into users(id,username,birthday,salary) values(null,"JERRY","1989-11-11",20000.0);
insert into users(id,username,birthday,salary) values(null,"ROWS","1990-11-11",30000.0);
insert into users(id,username,birthday,salary) values(null,"LUCY","1991-11-11",40000.0);
insert into users(id,username,birthday,salary) values(null,"JACK","1992-11-11",50000.0);

數據庫連接四要素:

driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.200.111:3306/nginx_db
username=root
password=123456

(2)API學習

1)引入"resty.mysql"模塊

local mysql = require "resty.mysql"

2)new 創建一個MySQL連接對象,遇到錯誤時,db為nil,err為錯誤描述信息

語法: db,err = mysql:new()

3)connect 嘗試連接到一個MySQL服務器

語法:ok,err=db:connect(options),options是一個參數的Lua表結構,里面包含數據庫連接的相關信息

  • host:服務器主機名或IP地址

  • port:服務器監聽端口,默認為3306

  • user:登錄的用戶名

  • password:登錄密碼

  • database:使用的數據庫名

4)set_timeout 設置子請求的超時時間(ms),包括connect方法

語法:db:set_timeout(time)

5)close 關閉當前MySQL連接並返回狀態。如果成功,則返回1;如果出現任何錯誤,則將返回nil和錯誤描述。

語法:db:close()

6)send_query 異步向遠程MySQL發送一個查詢。如果成功則返回成功發送的字節數;如果錯誤,則返回nil和錯誤描述

語法:bytes,err=db:send_query(sql)

7)read_result 從MySQL服務器返回結果中讀取一行數據。res返回一個描述OK包或結果集包的Lua表,語法:

res, err, errcode, sqlstate = db:read_result()

res, err, errcode, sqlstate = db:read_result(rows) :rows指定返回結果集的最大值,默認為4

如果是查詢,則返回一個容納多行的數組。每行是一個數據列的key-value對,如

{
{id=1,username="TOM",birthday="1988-11-11",salary=10000.0},
{id=2,username="JERRY",birthday="1989-11-11",salary=20000.0}
}

如果是增刪改,則返回類上如下數據

{
insert_id = 0,
server_status=2,
warning_count=1,
affected_rows=2,
message=nil
}

返回值:

  • res:操作的結果集

  • err:錯誤信息·

  • errcode:MySQL的錯誤碼,比如1064

  • sqlstate:返回由5個字符組成的標准SQL錯誤碼,比如42000

(3)效果實現

location /Mysql{
default_type "text/html";
content_by_lua_block{
-- 引入resty.mysql模塊
local mysql = require "resty.mysql"
-- 創建連接對象 new
local db = mysql:new()
-- 簡歷連接 傳入數據庫連接的相關信息
local ok,err = db:connect{
host="192.168.5.4",
port=3306,
user="root",
password="123456",
database="nginx_db"
}
-- 設置超時時間 set_timeout
db:set_timeout(1000)
-- 發送 SQL 語句
db:send_query("select * from users where id =1")
-- 讀取返回的結果,並且吧結果輸出到頁面
local res,err,errcode,sqlstate = db:read_result()
ngx.say(res[1].id..","..res[1].username..","..res[1].birthday..","..res[1].salary)
-- 關閉連接
db:close()
}
}

image-20211209103754097

問題:

  • 如何獲取返回數據的內容

  • 如何實現查詢多條數據

  • 如何實現數據庫的增刪改操作

10.4.4 使用lua-cjson處理查詢結果

通過上述的案例,read_result()得到的結果res都是table類型,要想在頁面上展示,就必須知道table的具體數據結構才能進行遍歷獲取。處理起來比較麻煩,接下來我們介紹一種簡單方式cjson,使用它就可以將table類型的數據轉換成json字符串,把json字符串展示在頁面上即可。具體如何使用?

(1)引入cjson

local cjson = require "cjson"

(2)調用cjson的encode方法進行類型轉換

cjson.encode(res) 

(3)使用

location /Mysql{
default_type "text/html";
content_by_lua_block{
-- 引入cjson 模塊
local cjson = require "cjson"
-- 引入resty.mysql模塊
local mysql = require "resty.mysql"
-- 創建連接對象 new
local db = mysql:new()
-- 簡歷連接 傳入數據庫連接的相關信息
local ok,err = db:connect{
host="192.168.5.4",
port=3306,
user="root",
password="123456",
database="nginx_db"
}
-- 設置超時時間 set_timeout
db:set_timeout(1000)
-- 發送 SQL 語句
db:send_query("select * from users where id =1")
-- 讀取返回的結果,並且吧結果輸出到頁面
local res,err,errcode,sqlstate = db:read_result()
ngx.say(cjson.encode(res))
for i,v in ipairs(res) do
ngx.say(v.id..","..v.username..","..v.birthday..","..v.salary)
end
-- 關閉連接
db:close()
}
}

10.4.5 lua-resty-mysql實現數據庫的增刪改

優化send_query和read_result,本方法是send_query和read_result組合的快捷方法。

語法:

res, err, errcode, sqlstate = db:query(sql[,rows])

有了該API,上面的代碼我們就可以進行對應的優化,如下:

location /Mysql{
default_type "text/html";
content_by_lua_block{
-- 引入resty.mysql模塊
local mysql = require "resty.mysql"
-- 創建連接對象 new
local db = mysql:new()
-- 簡歷連接 傳入數據庫連接的相關信息
local ok,err = db:connect{
host="192.168.5.4",
port=3306,
user="root",
password="123456",
database="nginx_db"
}
-- 設置超時時間 set_timeout
db:set_timeout(1000)
-- 查詢
local sql = "select * from users"
local res,err,errcode,sqlstate = db:query(sql)
-- 新增
local sql = "insert into users(id,username,birthday,salary) values(null,'zhangsan','2020-11-11',32222.0)"
local res,err,errcode,sqlstate = db:query(sql)
-- 修改
local sql = "update users set username='lisi' where id = 6"
local res,err,errcode,sqlstate = db:query(sql)
-- 刪除
local sql = "delete from users where id = 6"
local res,err,errcode,sqlstate = db:query(sql)
-- 關閉連接
db:close()
}
}

10.5 綜合小案例

使用ngx_lua模塊完成Redis緩存預熱。

分析:

(1)先得有一張表(users)

(2)瀏覽器輸入如下地址

http://191.168.5.3?username=TOM

(3)從表中查詢出符合條件的記錄,此時獲取的結果為table類型

(4)使用cjson將table數據轉換成json字符串

(5)將查詢的結果數據存入Redis中

init_by_lua_block{
-- 引入所需模塊
redis = require "resty.redis"
mysql = require "resty.mysql"
cjson = require "cjson"
}
location /{
default_type "text/html";
content_by_lua_block{

--獲取請求的參數username
local param = ngx.req.get_uri_args()["username"]
--建立mysql數據庫的連接
local db = mysql:new()
local ok,err = db:connect{
host="192.168.5.4",
port=3306,
user="root",
password="123456",
database="nginx_db"
}
if not ok then
ngx.say("failed connect to mysql:",err)
return
end
--設置連接超時時間
db:set_timeout(1000)
--查詢數據
local sql = "";
if not param then
sql="select * from users"
else
sql="select * from users where username=".."'"..param.."'"
end
local res,err,errcode,sqlstate=db:query(sql)
if not res then
ngx.say("failed to query from mysql:",err)
return
end
--連接redis
local rd = redis:new()
ok,err = rd:connect("192.168.5.4",6379)
if not ok then
ngx.say("failed to connect to redis:",err)
return
end
rd:set_timeout(1000)
--循環遍歷數據
for i,v in ipairs(res) do
rd:set("user_"..v.username,cjson.encode(v))
end
ngx.say("success")
rd:close()
db:close()
}
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM