天天看點

Lua腳本基礎入門及其案例

Lua介紹

lua是什麼?

Lua [1] 是一個小巧的腳本語言。它是于1993年開發的。 其設計目的是為了通過靈活嵌入應用程式中進而為應用程式提供靈活的擴充和定制功能。Lua由标準C編寫而成,幾乎在所有作業系統和平台上都可以編譯,運作。Lua并沒有提供強大的庫,這是由它的定位決定的。是以Lua不适合作為開發獨立應用程式的語言。Lua 有一個同時進行的JIT項目,提供在特定平台上的即時編譯功能。

簡單來說:

Lua 是一種輕量小巧的腳本語言,用标準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程式中,進而為應用程式提供靈活的擴充和定制功能。

lua的特性

支援面向過程(procedure-oriented)程式設計和函數式程式設計(functional programming);

自動記憶體管理;隻提供了一種通用類型的表(table),用它可以實作數組,哈希表,集合,對象;

語言内置模式比對;閉包(closure);函數也可以看做一個值;提供多線程(協同程序,并非作業系統所支援的線程)支援;

通過閉包和table可以很友善地支援面向對象程式設計所需要的一些關鍵機制,比如資料抽象,虛函數,繼承和重載等。

lua的應用場景

遊戲開發

獨立應用腳本

Web 應用腳本

擴充和資料庫插件如:MySQL Proxy 和 MySQL WorkBench

安全系統,如入侵檢測系統

redis中嵌套調用實作類似事務的功能

web容器中應用處理一些過濾 緩存等等的邏輯,例如nginx。

lua的安裝

有linux版本的安裝也有mac版本的安裝。。我們采用linux版本的安裝,首先我們準備一個linux虛拟機。

安裝步驟,在linux系統中執行下面的指令。

curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
tar zxf lua-5.3.5.tar.gz
cd lua-5.3.5
make linux test
      

注意:此時安裝,有可能會出現如下錯誤:

Lua腳本基礎入門及其案例

此時需要安裝lua相關依賴庫的支援,執行如下指令即可:

yum install libtermcap-devel ncurses-devel libevent-devel readline-devel
      

此時再執行lua測試看lua是否安裝成功

[root@localhost ~]# lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
      

入門程式

建立hello.lua檔案,内容為

編輯檔案hello.lua

vi hello.lua
      

在檔案中輸入:

print("hello");
print("hello lua!");
print("hello world!");
      

儲存并退出。

執行指令

lua hello.lua
      

輸出為:

hello
hello lua!
hello world!
      

效果如下:

Lua腳本基礎入門及其案例

LUA的基本文法(了解)

lua有互動式程式設計和腳本式程式設計。互動式程式設計就是直接輸入文法,就能執行。腳本式程式設計需要編寫腳本,然後再執行指令 執行腳本才可以。一般采用腳本式程式設計。(例如:編寫一個hello.lua的檔案,輸入檔案内容,并執行lua hell.lua即可)

(1)互動式程式設計

Lua 提供了互動式程式設計模式。我們可以在指令行中輸入程式并立即檢視效果。

Lua 互動式程式設計模式可以通過指令 lua -i 或 lua 來啟用:

lua -i
      

如下圖:

Lua腳本基礎入門及其案例

2)腳本式程式設計

我們可以将 Lua 程式代碼保持到一個以 lua 結尾的檔案,并執行,該模式稱為腳本式程式設計,例如上面入門程式中将lua文法寫到hello.lua檔案中。

注釋

一行注釋:兩個減号是單行注釋:

--
      

多行注釋:

--[[
 多行注釋
 多行注釋
 --]]
      

定義變量

全局變量,預設的情況下,定義一個變量都是全局變量,

如果要用局部變量 需要聲明為local.例如:

-- 全局變量指派
a=1
-- 局部變量指派
local b=2 
      

如果變量沒有初始化:則 它的值為nil 這和java中的null不同。

如下圖案例:

Lua腳本基礎入門及其案例

Lua中的資料類型

Lua 是動态類型語言,變量不要類型定義,隻需要為變量指派。 值可以存儲在變量中,作為參數傳遞或結果傳回。

Lua 中有 8 個基本類型分别為:nil、boolean、number、string、userdata、function、thread 和 table。

Lua腳本基礎入門及其案例

執行個體:

print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil
      

流程控制

(1)if語句

Lua if 語句 由一個布爾表達式作為條件判斷,其後緊跟其他語句組成。

文法:

if(布爾表達式)
then
   --[ 在布爾表達式為 true 時執行的語句 --]
end
      
Lua腳本基礎入門及其案例

(2)if…else語句

Lua if 語句可以與 else 語句搭配使用, 在 if 條件表達式為 false 時執行 else 語句代碼塊。

if(布爾表達式)
then
   --[ 布爾表達式為 true 時執行該語句塊 --]
else
   --[ 布爾表達式為 false 時執行該語句塊 --]
end
      
Lua腳本基礎入門及其案例

循環

學員完成

(1)while循環[滿足條件就循環]

Lua 程式設計語言中 while 循環語句在判斷條件為 true 時會重複執行循環體語句。

while(condition)
do
   statements
end
      
a=10
while( a < 20 )
do
   print("a 的值為:", a)
   a = a+1
end
      
Lua腳本基礎入門及其案例

(2)for循環

Lua 程式設計語言中 for 循環語句可以重複執行指定語句,重複次數可在 for 語句中控制。

文法: 1->10 1:exp1 10:exp2 2:exp3:遞增的數量

for var=exp1,exp2,exp3 
do  
    <執行體>  
end  
      

var 從 exp1 變化到 exp2,每次變化以 exp3 為步長遞增 var,并執行一次 “執行體”。exp3 是可選的,如果不指定,預設為1。

例子:

for i=1,9,2
do
   print(i)
end
      

for i=1,9,2

:i=1從1開始循環,9循環資料到9結束,2每次遞增2

Lua腳本基礎入門及其案例

(3)repeat…until語句[滿足條件結束]

Lua 程式設計語言中 repeat…until 循環語句不同于 for 和 while循環,for 和 while 循環的條件語句在目前循環執行開始時判斷,而 repeat…until 循環的條件語句在目前循環結束後判斷。

repeat
   statements
until( condition )
      

案例:

Lua腳本基礎入門及其案例

函數

lua中也可以定義函數,類似于java中的方法。例如:

--[[ 函數傳回兩個值的最大值 --]]
function max(num1, num2)

   if (num1 > num2) then
      result = num1;
   else
      result = num2;
   end

   return result; 
end
-- 調用函數
print("兩值比較最大值為 ",max(10,4))
print("兩值比較最大值為 ",max(5,6))
      

執行之後的結果:

兩值比較最大值為     10
兩值比較最大值為     6
      

…:表示拼接

table 是 Lua 的一種資料結構用來幫助我們建立不同的資料類型,如:數組、字典等。

Lua也是通過table來解決子產品(module)、包(package)和對象(Object)的。

-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"

-- 移除引用
mytable = nil
      

子產品

(1)子產品定義

子產品類似于一個封裝庫,從 Lua 5.1 開始,Lua 加入了标準的子產品管理機制,可以把一些公用的代碼放在一個檔案裡,以 API 接口的形式在其他地方調用,有利于代碼的重用和降低代碼耦合度。

建立一個檔案叫module.lua,在module.lua中建立一個獨立的子產品,代碼如下:

-- 檔案名為 module.lua
-- 定義一個名為 module 的子產品
module = {}
 
-- 定義一個常量
module.constant = "這是一個常量"
 
-- 定義一個函數
function module.func1()
    print("這是一個公有函數")
end
 
local function func2()
    print("這是一個私有函數!")
end
 
function module.func3()
    func2()
end
 
return module
      

由上可知,子產品的結構就是一個 table 的結構,是以可以像操作調用 table 裡的元素那樣來操作調用子產品裡的常量或函數。

上面的 func2 聲明為程式塊的局部變量,即表示一個私有函數,是以是不能從外部通路子產品裡的這個私有函數,必須通過子產品裡的公有函數來調用.

(2)require 函數

require 用于 引入其他的子產品,類似于java中的類要引用别的類的效果。

用法:

require("<子產品名>")
      
require "<子產品名>"
      

兩種都可以。

我們可以将上面定義的module子產品引入使用,建立一個test_module.lua檔案,代碼如下:

-- test_module.lua 檔案
-- module 子產品為上文提到到 module.lua
require("module")

print(module.constant)

module.func3()
      

OpenResty介紹

OpenResty(又稱:ngx_openresty) 是一個基于 nginx的可伸縮的 Web 平台,由中國人章亦春發起,提供了很多高品質的第三方子產品。

OpenResty 是一個強大的 Web 應用伺服器,Web 開發人員可以使用 Lua 腳本語言調動 Nginx 支援的各種 C 以及 Lua 子產品,更主要的是在性能方面,OpenResty可以 快速構造出足以勝任 10K 以上并發連接配接響應的超高性能 Web 應用系統。

360,UPYUN,阿裡雲,新浪,騰訊網,去哪兒網,酷狗音樂等都是 OpenResty 的深度使用者。

OpenResty 簡單了解成 就相當于封裝了nginx,并且內建了LUA腳本,開發人員隻需要簡單的其提供了子產品就可以實作相關的邏輯,而不再像之前,還需要在nginx中自己編寫lua的腳本,再進行調用了。

安裝openresty

linux安裝openresty:

1.添加倉庫執行指令

yum install yum-utils
 yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
      

2.執行安裝

yum install openresty
      

3.安裝成功後 會在預設的目錄如下:

/usr/local/openresty
      

安裝nginx

預設已經安裝好了nginx,在目錄:/usr/local/openresty/nginx 下。

修改/usr/local/openresty/nginx/conf/nginx.conf,将配置檔案使用的根設定為root,目的就是将來要使用lua腳本的時候 ,直接可以加載在root下的lua腳本。

cd /usr/local/openresty/nginx/conf
vi nginx.conf
      

修改代碼如下:

Lua腳本基礎入門及其案例

測試通路

重新開機下centos虛拟機,然後通路測試Nginx

通路位址:

http://伺服器ip位址/

Lua腳本基礎入門及其案例

Lua腳本執行個體:頁面廣告緩存的載入與讀取

需求分析

需要在頁面上顯示廣告的資訊。

Lua腳本基礎入門及其案例

Lua+Nginx配置

(1)實作思路-查詢資料放入redis中

實作思路:

定義請求:用于查詢資料庫中的資料更新到redis中。

a.連接配接mysql ,按照廣告分類ID讀取廣告清單,轉換為json字元串。

b.連接配接redis,将廣告清單json字元串存入redis 。

定義請求:

請求:
    /update_content
參數:
    id  --指定廣告分類的id
傳回值:
    json
      

請求位址:

<http://伺服器IP位址/update_content?id=1>

建立

/root/lua

目錄,在該目錄下建立

update_content.lua`: 目的就是連接配接mysql 查詢資料 并存儲到redis中。

Lua腳本基礎入門及其案例

上圖代碼:

ngx.header.content_type="application/json;charset=utf8"
local cjson = require("cjson")
local mysql = require("resty.mysql")
local uri_args = ngx.req.get_uri_args()
local id = uri_args["id"]

local db = mysql:new()
db:set_timeout(1000)
local props = {
    host = "8.131.64.45",
    port = 3306,
    database = "changgou_content",
    user = "root",
    password = "[email protected]"
}

local res = db:connect(props)
local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order"
res = db:query(select_sql)
db:close()

local redis = require("resty.redis")
local red = redis:new()
red:set_timeout(2000)

local ip ="8.131.66.136"
local port = 6379
red:connect(ip,port)
red:auth("csp19990129")
red:select(0)
red:set("content_"..id,cjson.encode(res))
red:close()

ngx.say("{flag:true}")
      

修改

/usr/local/openresty/nginx/conf/nginx.conf

檔案: 添加頭資訊,和 location資訊:

Lua腳本基礎入門及其案例

代碼如下:

server {
    listen       80;
    server_name  localhost;
    
    # 使用者請求/update_content?id=1,将請求給lua腳本進行處理
    location /update_content {
        content_by_lua_file /root/lua/update_content.lua;
    }
}
      

定義lua緩存命名空間,修改nginx.conf,添加如下代碼即可:

Lua腳本基礎入門及其案例
# lua緩存命名空間   
lua_shared_dict dis_cache 128m; 
      

浏覽器測試執行緩存:

http://伺服器ip/update_content?id=1

Lua腳本基礎入門及其案例

去redis用戶端測試,緩存是否成功:

Lua腳本基礎入門及其案例

如圖,獲得到了資料,緩存成功!

(2)實作思路-從redis中擷取資料

定義請求,使用者根據廣告分類的ID 擷取廣告的清單。通過lua腳本直接從redis中擷取資料即可。

請求:/read_content
參數:id
傳回值:json
      

在/root/lua目錄下建立read_content.lua:

--設定響應頭類型
ngx.header.content_type="application/json;charset=utf8"
--擷取請求中的參數ID
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
--引入redis庫
local redis = require("resty.redis");
--建立redis對象
local red = redis:new()
--設定逾時時間
red:set_timeout(2000)
--連接配接
local ok, err = red:connect("伺服器ip", 6379)
--擷取key的值
local rescontent=red:get("content_"..id)
--輸出到傳回響應中
ngx.say(rescontent)
--關閉連接配接
red:close()
      

/usr/local/openresty/nginx/conf/nginx.conf

中配置如下:

如圖:

Lua腳本基礎入門及其案例

代碼:

# 使用者請求/read_content?id=1,将請求給lua腳本進行擷取緩存/資料庫中的資料 
location /read_content {
     content_by_lua_file /root/lua/read_content.lua;
}

      

3)加入openresty本地緩存

如上的方式沒有問題,但是如果請求都到redis,redis壓力也很大,是以我們一般采用多級緩存的方式來減少下遊系統的服務壓力。參考基本思路圖的實作。

先查詢openresty本地緩存 如果沒有

再查詢redis中的資料,如果沒有

再查詢mysql中的資料,但凡有資料 則傳回即可。

修改read_content.lua檔案,代碼如下:

Lua腳本基礎入門及其案例

完整配置如下:

ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
--擷取本地緩存
local cache_ngx = ngx.shared.dis_cache;
--根據ID 擷取本地緩存資料
local contentCache = cache_ngx:get('content_cache_'..id);

if contentCache == "" or contentCache == nil then
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    red:connect("8.xxx.xx.xx6", 6379)
    --密碼和選擇的桶
    red:auth("csxxxxx29")
    red:select(0)

    local rescontent=red:get("content_"..id);

    if ngx.null == rescontent then
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(2000)
        local props = {
            host = "8.xxx.xx.x5",
            port = 3306,
            database = "changgou_content",
            user = "root",
            password = "CSPxxxxxx.com"
        }
        local res = db:connect(props);
        local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";
        res = db:query(select_sql);
        local responsejson = cjson.encode(res);
        red:set("content_"..id,responsejson);
        ngx.say(responsejson);
        db:close()
    else
        cache_ngx:set('content_cache_'..id, rescontent, 10*60);
        ngx.say(rescontent)
    end
    red:close()
else
    ngx.say(contentCache)
end
      

測試位址:

http://192.168.211.132/update_content?id=1

此時會将分類ID=1的所有廣告查詢出來,并存入到Redis緩存:

Lua腳本基礎入門及其案例

http://伺服器ip/update_content?id=1

此時會擷取分類ID=1的所有廣告資訊:

Lua腳本基礎入門及其案例