首頁 > 軟體

nginx 整合lua操作mysql的過程解析

2022-05-15 19:00:11

前言

lua是一誇小巧,靈活且高效的指令碼語言,用標準C語言編寫並以原始碼形式開發,在很多業務場景下配合適當的設計,可以帶來意想不到的效果;

舉個常見的例子,現在幾乎很多公司都會用到nginx作為代理伺服器,假如現在有這麼個需求,需要做黑名單過濾,或者在閘道器這一層做流控,這該怎麼做呢?

實現思路

  • 直接在nginx做設定黑名單,通過編寫邏輯塊實現;
  • 在伺服器端(Java)中編寫過濾器,在過濾器中統一攔截;
  • 在伺服器端(Java)中編寫攔截器,在攔截器中統一攔截;

這裡列舉了3種實現的思路,至於實現方案,可能還有更多,但是我們想想,在nginx中編寫邏輯塊貌似不是很多人擅長的;在程式碼層面做不是不可以,而是這樣一來,在涉及到高並行的業務高峰期,這必然會對後端服務造成較大的壓力,那麼還有沒有其他更好的處理辦法呢?

這就是要說的lua,即nginx作為閘道器仍然作為代理伺服器,由於nginx可以整合lua,於是使用lua進行配合,來完成上面的業務實現的設計;

ngx_lua模組概念

  • ngx_lua模組由淘寶技術團隊開發,通過將lua直譯器整合進Nginx;
  • 可採用lua指令碼實現業務邏輯,由於lua的緊湊、快速以及內建協程,所以在保證高並行服務能力的同時極大地降低了業務邏輯實現成本;
  • OpenRestry

  • OpenResty是一個基於Nginx與 Lua 的高效能 Web 平臺,其內部整合了大量精良的 Lua庫、第三方模組以及大多數的依賴項;用於方便地搭建能夠處理超高並行、擴充套件性極高的動態 Web 應用、Web 服務和動態閘道器;
  • OpenResty內部已經整合了Nginx和Lua,所以使用起來會更加方便;

簡單來說,直接安裝並使用OpenRestry,就可以達到同時使用Nginx與Lua的效果,同時基於OpenRestry,還可以在內部操作其他中介軟體,比如mysql,redis,kafka等,這樣就使得業務架構在設計上具備了更大的靈活性;

OpenRestry安裝步驟

1、下載OpenRestry

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

2、解壓縮檔案

tar -zxf openresty-1.15.8.2.tar.gz

3、進入OpenResty目錄執行設定

這一步有點類似於nginx的原始碼安裝,進行相關的環境變數的設定,這裡直接使用預設的就好;

./configure

4、 執行命令:make && make install

5、進入OpenResty的目錄設定nginx

進入nginx目錄,可以看到裡面的目錄和nginx自身安裝完畢後的設定幾乎一樣

進入conf,找到nginx.conf組態檔,新增如下內容:

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

6、啟動nginx並測試

進入nginx的sbin目錄下啟動nginx

啟動完成後,瀏覽器存取下伺服器即可,可以看到nginx本身服務已啟動

然後存取上面設定的lua地址,可以看到也能夠正常的存取到,說明openrestry的模組已經安裝完畢

ngx_lua常用指令

使用Lua編寫Nginx指令碼的基本構建塊是指令,指令用於指定何時執行使用者Lua程式碼以及如何使用結果,下面針對一些常用的指令做簡單的說明

1、init_by_lua*

該指令在每次Nginx重新載入設定時執行,用來完成一些耗時操作模組載入,或初始化一些全域性設定

2、init_worker_by_lua*

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

3、set_by_lua*

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

4、rewrite_by_lua*

用於執行內部URL重寫或者外部重定向,典型的如偽靜態化URL重 寫,本階段在rewrite處理階段的最後預設執行(和nginx自身的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程式碼

一個使用指令的需求

接下來針對上面提到的各種指令,來做一個簡單的需求

nginx接收到請求後,根據引數中gender傳入的值,如果gender傳入的是1 則在頁面上展示 “先生” , 如果gender傳入的是0,則在頁面上展示“女士”

程式碼實現

注意:使用指令編寫的基本步驟是,在nginx.conf模組中,自定義localtion塊中編寫lua的相關程式碼即可

location /getByGender {
          default_type 'text/html';
          set_by_lua $param "

                local uri_args = ngx.req.get_uri_args()
                local gender = uri_args['gender']
                local name = uri_args['name']
                if gender =='1' then
                       return name..':先生'
                elseif gender=='0' then
                       return name..':女士'
                else
                       return name
                end
          ";
          charset utf-8;
          return 200 $param;
 }

然後啟動nginx做一下測試

1)存取服務,不攜帶任何引數

這時候無任何返回資訊

2)存取服務,攜帶name引數

3)存取服務,攜帶name和gender引數

更多的指令可以參照此類方式編寫,但是前提需要掌握一點lua的基本語法

lua操作redis

Redis在系統中經常作為資料快取、記憶體資料庫使用,在各類網際網路專案中扮演著非常重要的作用;

Lua-resty-redis庫是OpenResty提供的一個操作Redis的介面庫,可根據自己的業務情況做一些邏輯處理,適合做複雜的業務邏輯。所以下面將以Lua-resty-redis來進行說明。

lua-resty-redis環境準備

1、提前安裝好redis並啟動服務

2、測試下redis使用者端

lua-resty-redis提供了存取Redis的詳細API,包括建立對接、連 接、操作、資料處理等。這些API基本上與Redis的操作是對應起來的

lua-resty-redis常用API

1、lua中匯入redis依賴

redis = require "resty.redis"

2、new,建立一個Redis物件

redis,err = redis:new()

3、建立redis連線

  • ok:連線成功返回 1,連線失敗返回nil;
  • err:返回對應的錯誤資訊;

ok,err=redis:connect(host,port[,options_table])

4、設定請求操作Redis的超時時間

redis:set_timeout(time)

5、close,關閉連線

  • 關閉當前連線,成功返回1;
  • 失敗返回nil和錯誤資訊;

ok,err = redis:close()

補充說明:

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

具體實現效果展示

在nginx.conf模組下,新增如下的location內容

location /redis {
    default_type "text/html";
    content_by_lua_block {
        local redis = require "resty.redis"     -- 引入 Redis
        local redisObj = redis:new()            --建立Redis物件
        redisObj:set_timeout(3000)              --設定超時資料為3s
        local ok,err = redisObj:connect("IP",6379)    --設定redis連線資訊
        if not ok then                          --判斷是否連線成功
            ngx.say("failed to connection redis",err)
            return
        end
        ok,err = redisObj:set("username","TOM")     --存入 資料
        if not ok then                              --判斷是否存入成功
            ngx.say("failed to set username",err)
            return
        end
        local res,err = redisObj:get("username")    --從 redis中獲取資料
        ngx.say(res) --將資料寫會訊息體中
        redisObj:close()
    }
}

重啟nginx,進行測試,直接在瀏覽器存取一下如下地址,可以看到資料成功寫入到redis


ngx_lua操作Mysql

MySQL是一個使用廣泛的關係型資料庫。在ngx_lua中,MySQL有兩種存取模式,分別是

  • 用ngx_lua模組和lua-resty-mysql模組: 這兩個模組是安裝OpenResty時預設安裝的;
  • 使用drizzle_nginx_module(HttpDrizzleModule)模組:需要單獨

安裝,這個庫現不在OpenResty中

lua-resty-mysql

lua-resty-mysql是OpenResty開發的模組,使用靈活、功能強大,適合複雜的業務場景,同時支援儲存過程存取;

lua-resty-mysql實現資料庫查詢

1、準備好mysql服務

2、提前建立一張表

CREATE TABLE `users` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `birthday` date DEFAULT NULL,
  `salary` double(10,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

並提前準備幾條資料

INSERT INTO `mydb`.`users` (`id`, `username`, `birthday`, `salary`) VALUES ('1', 'xiaowang', '1991-03-15', '9000.00');
INSERT INTO `mydb`.`users` (`id`, `username`, `birthday`, `salary`) VALUES ('2', 'xiaoma', '1992-07-15', '8000.00');

lua-resty-mysql API說明

1、引入"resty.mysql"模組

local mysql = require "resty.mysql"

2、建立MySQL連線物件

遇到錯誤時,db為nil,err為錯誤描 述資訊

db,err = mysql:new()

3、建立連線物件

ok,err=db:connect(options)

options是一個引數的 Lua表結構,裡面包含資料庫連線的相關資訊

  • host:伺服器主機名或IP地址
  • port:伺服器監聽埠,預設為3306
  • user:登入的使用者名稱
  • password:登入密碼
  • database:使用的資料庫名

4、設定子請求的超時時間(ms)

包括connect方法

db:set_timeout(time)

5、關閉當前MySQL連線並返回狀態

如果成功,則返回1;如果出現任 何錯誤,則將返回nil和錯誤描述

db:close()

6、非同步向遠端MySQL傳送一個查詢

如果成功則返回成功傳送的位元組 數;如果錯誤,則返回nil和錯誤描述

bytes,err=db:send_query(sql)

7、從MySQL伺服器返回結果中讀取一行資料

  • res返回一個描述OK包 或結果集包的Lua表
  • rows指定返回結果集的最大值,預設為4
  • 如果是查詢,則返回一個容納多行的陣列。每行是一個資料列的 key-value對
res, err, errcode, sqlstate = db:read_result() res, err, errcode, sqlstate = db:read_result(rows)

返回結果類似下面這樣

{ 
{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

具體操作案例

將下面的內容新增到server塊,然後重啟nginx

location /mysql {
     content_by_lua_block{
		default_type "text/html";
        local mysql = require "resty.mysql" 
        local db = mysql:new() 
        local ok,err = db:connect{ 
            host="127.0.0.1", 
            port=3306,
            user="root", 
            password="123456", 
            database="mydb"
        } 
            db:set_timeout(3000) 
            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()
    } 
}

可以看到,通過存取mysql這個路徑,成功查詢到資料庫中ID為1的這條資料

使用cjson對查詢結果進行格式化

從上面的返回結果來看,這種形式的返回資料在解析的時候其實並不是很友好,於是可以使用lua-cjson處理查詢結果

使用步驟

步驟一:引入cjson

local cjson = require “cjson”

步驟二:呼叫cjson的encode方法進行型別轉換

cjson.encode(res)

下面對上面程式模組做簡單的改造

location /mysql-cjson {
		default_type "text/html";
     content_by_lua_block{
     	local cjson = require "cjson"
        local mysql = require "resty.mysql" 
        local db = mysql:new() 
        local ok,err = db:connect{ 
            host="127.0.0.1", 
            port=3306,
            user="root", 
            password="123456", 
            database="mydb"
        } 
            db:set_timeout(3000) 
            db:send_query("select * from users") 
            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()
    } 
}

然後再次進行測試,這時候就以json的格式對資料進行了展現

增刪改操作

location /mysql-cjson {
		default_type "text/html";
     content_by_lua_block{
     	local cjson = require "cjson"
        local mysql = require "resty.mysql" 

        local db = mysql:new() 

        local ok,err = db:connect{ 
            host="127.0.0.1", 
            port=3306,
            user="root", 
            password="123456", 
            database="mydb"
        } 
            db:set_timeout(3000) 
            -- 查詢操作
            db:send_query("select * from users where id=1") 
			-- 插入資料
			--local res,err,errcode,sqlstate = db:query("insert into users(id,username,birthday,salary) values(3,'lifei','1995-10-17',3000)")
			-- 修改資料
			--local res,err,errcode,sqlstate = db:query("update users set username='lisi' where id = 1")
			-- 刪除資料
			--local res,err,errcode,sqlstate = db:query("delete from users where id = 2")
            db:close()
    } 
}

到此這篇關於nginx 整合lua操作mysql的過程解析的文章就介紹到這了,更多相關nginx lua操作mysql內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com