一、引言
Redis學了一段時間了,基本的東西都沒問題了。從今天開始講寫一些redis和lua腳本的相關的東西,lua這個腳本是一個好東西,可以運行在任何平台上,也可以嵌入到大多數語言當中,來擴展其功能。lua腳本是用C語言寫的,體積很小,運行速度很快,並且每次的執行都是作為一個原子事務來執行的,我們可以在其中做很多的事情。由於篇幅很多,一次無法概述全部,這個系列可能要通過多篇文章的形式來寫,好了,今天我們進入正題吧。
二、Lua簡介
Lua 是一個小巧的腳本語言。是巴西里約熱內盧天主教大學(Pontifical Catholic University of Rio de Janeiro)里的一個研究小組,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所組成並於1993年開發。 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。Lua由標准C編寫而成,幾乎在所有操作系統和平台上都可以編譯,運行。Lua並沒有提供強大的庫,這是由它的定位決定的。所以Lua不適合作為開發獨立應用程序的語言。Lua 有一個同時進行的JIT項目,提供在特定平台上的即時編譯功能。
Lua腳本可以很容易的被C/C++ 代碼調用,也可以反過來調用C/C++的函數,這使得Lua在應用程序中可以被廣泛應用。不僅僅作為擴展腳本,也可以作為普通的配置文件,代替XML,ini等文件格式,並且更容易理解和維護。 Lua由標准C編寫而成,代碼簡潔優美,幾乎在所有操作系統和平台上都可以編譯,運行。一個完整的Lua解釋器不過200k,在目前所有腳本引擎中,Lua的速度是最快的。這一切都決定了Lua是作為嵌入式腳本的最佳選擇。
三、使用Lua腳本的好處
1、減少網絡開銷:可以將多個請求通過腳本的形式一次發送,減少網絡時延和請求次數。
2、原子性的操作:Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
3、代碼復用:客戶端發送的腳步會永久存在redis中,這樣,其他客戶端可以復用這一腳本來完成相同的邏輯。
4、速度快:見 與其它語言的性能比較, 還有一個 JIT編譯器可以顯著地提高多數任務的性能; 對於那些仍然對性能不滿意的人, 可以把關鍵部分使用C實現, 然后與其集成, 這樣還可以享受其它方面的好處。
5、可以移植:只要是有ANSI C 編譯器的平台都可以編譯,你可以看到它可以在幾乎所有的平台上運行:從 Windows 到Linux,同樣Mac平台也沒問題, 再到移動平台、游戲主機,甚至瀏覽器也可以完美使用 (翻譯成JavaScript).
6、源碼小巧:20000行C代碼,可以編譯進182K的可執行文件,加載快,運行快。
四、redis和lua整合詳解
1、調用Lua腳本的語法:
$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
--eval,告訴redis-cli讀取並運行后面的lua腳本
path/to/redis.lua,是lua腳本的位置
KEYS[1] KEYS[2],是要操作的鍵,可以指定多個,在lua腳本中通過KEYS[1], KEYS[2]獲取
ARGV[1] ARGV[2],參數,在lua腳本中通過ARGV[1], ARGV[2]獲取。
注意: KEYS和ARGV中間的 ',' 兩邊的空格,不能省略。
redis支持大部分Lua標准庫
庫名 | 說明 |
---|---|
Base | 提供一些基礎函數 |
String | 提供用於字符串操作的函數 |
Table | 提供用於表操作的函數 |
Math | 提供數學計算函數 |
Debug | 提供用於調試的函數 |
2、在腳本中調用redis命令
在腳本中可以使用redis.call函數調用Redis命令
redis.call('set', 'foo', 'bar')
local value=redis.call('get', 'foo') --value的值為bar
redis.call函數的返回值就是Redis命令的執行結果
Redis命令的返回值有5種類型,redis.call函數會將這5種類型的回復轉換成對應的Lua的數據類型,具體的對應規則如下(空結果比較特殊,其對應Lua的false)
redis返回值類型和Lua數據類型轉換規則
redis返回值類型 | Lua數據類型 |
---|---|
整數回復 | 數字類型 |
字符串回復 | 字符串類型 |
多行字符串回復 | table類型(數組形式) |
狀態回復 | table類型(只有一個ok字段存儲狀態信息) |
錯誤回復 | table類型(只有一個err字段存儲錯誤信息) |
redis還提供了redis.pcall函數,功能與redis.call相同,唯一的區別是當命令執行出錯時,redis.pcall會記錄錯誤並繼續執行,而redis.call會直接返回錯誤,不會繼續執行。在腳本中可以使用return語句將值返回給客戶端,如果沒有執行return語句則默認返回nil
Lua數據類型和redis返回值類型轉換規則
Lua數據類型 | redis返回值類型 |
---|---|
數字類型 | 整數回復(Lua的數字類型會被自動轉換成整數) |
字符串類型 | 字符串回復 |
table類型(數組形式) | 多行字符串回復 |
table類型(只有一個ok字段存儲狀態信息) | 狀態回復 |
table類型(只有一個err字段存儲錯誤信息) | 錯誤回復 |
3、腳本相關命令
EVAL語法: eval script numkeys key [key ...] arg [arg ...]
通過key和arg這兩類參數向腳本傳遞數據,它們的值在腳本中分別使用KEYS和ARGV兩個表類型的全局變量訪問。
script: 是lua腳本
numkeys:表示有幾個key,分別是KEYS[1],KEYS[2]...,如果有值,從第numkeys+1個開始就是參數值,ARGV[1],ARGV[2]...
注意: EVAL命令依據參數numkeys來將其后面的所有參數分別存入腳本中KEYS和ARGV兩個table類型的全局變量。當腳本不需要任何參數時,也不能省略這個參數(設為0)
192.168.127.128:6379>eval "return redis.call('set',KEYS[1],ARGV[1])" 1 name liulei OK 192.168.127.128:6379>get name "liulei"
4、 EVALSHA命令
在腳本比較長的情況下,如果每次調用腳本都需要將整個腳本傳給Redis會占用較多的帶寬。為了解決這個問題,Redis提供了EVALSHA命令,允許開發者通過腳本內容的SHA1摘要來執行腳本,該命令的用法和EVAL一樣,只不過是將腳本內容替換成腳本內容的SHA1摘要。
Redis在執行EVAL命令時會計算腳本的SHA1摘要並記錄在腳本緩存中,執行EVALSHA命令時Redis會根據提供的摘要從腳本緩存中查找對應的腳本內容,如果找到了則執行腳本,否則會返回錯誤:"NOSCRIPT No matching script. Please use EVAL."
在程序中使用EVALSHA命令的一般流程如下。
1)、先計算腳本的SHA1摘要,並使用EVALSHA命令執行腳本。
2)、獲得返回值,如果返回“NOSCRIPT”錯誤則使用EVAL命令重新執行腳本。
雖然這一流程略顯麻煩,但值得慶幸的是很多編程語言的Redis客戶端都會代替開發者完成這一流程。執行EVAL命令時,先嘗試執行EVALSHA命令,如果失敗了才會執行EVAL命令。
SCRIPTLOAD "lua-script" 將腳本加入緩存,但不執行, 返回:腳本的SHA1摘要
SCRIPT EXISTS lua-script-sha1 判斷腳本是否已被緩存
5、 SCRIPT FLUSH(該命令不區分大小寫)
清空腳本緩存,redis將腳本的SHA1摘要加入到腳本緩存后會永久保留,不會刪除,但可以手動使用SCRIPT FLUSH命令情況腳本緩存。
192.168.127.128:6379>script flush OK 192.168.127.128:6379>SCRIPT FLUSH OK
6、SCRIPT KILL(該命令不區分大小寫)
強制終止當前腳本的執行。 但是,如果當前執行的腳步對redis的數據進行了寫操作,則SCRIPT KILL命令不會終止腳本的運行,以防止腳本只執行了一部分。腳本中的所有命令,要么都執行,要么都不執行。
192.168.127.128:6379>script kill (error)NOTBUSY No scripts in execution right now 192.168.127.128:6379>SCRIPT KILL (error)NOTBUSY No scripts in execution right now //這是當前沒有腳本在執行,所以提示該錯誤
7、lua-time-limit 5000(redis.conf配置文件中)
為了防止某個腳本執行時間過長導致Redis無法提供服務(比如陷入死循環),Redis提供了lua-time-limit參數限制腳本的最長運行時間,默認為5秒鍾。當腳本運行時間超過這一限制后,Redis將開始接受其他命令但不會執行(以確保腳本的原子性,因為此時腳本並沒有被終止),而是會返回“BUSY”錯誤。
五、安裝和使用Lua腳本
1、安裝lua類庫環境
1.1、yum install -y readline
1.2、yum install -y readline-devel
2、下載lua最新的版本安裝
2.1、去官網下載lua,可以直接通過wget下載,地址如下:http://www.lua.org/download.html
[root@lunux~]# wget http://www.lua.org/ftp/lua-5.3.4.tar.gz /root/software/download/lua/
2.2、通過ssh SSH Secure File Transfer Client工具,把軟件包上傳到Linux服務器上。目錄是:/root/software/download/lua/
[root@linux~]# cd ./software/download/lua/ [root@linux lua]# tar zxvf lua-5.3.4.tar.gz
2.3、進入到已經解壓的目錄lua-5.3.4,准備安裝文件。
[root@linux lua]# ls [root@linux lua]# lua-5.3.4 lua-5.3.4.tar.gz [root@linux lua]# cd lua-5.3.4 [root@linux lua-5.3.4]#
2.4、准備安裝環境,使用make linux命令,當前也是需要gcc命令的支持,事先必須安裝,安裝gcc命令:yum install gcc。
[root@linux lua-5.3.4]# make linux
2.5、開始安裝lua軟件包,使用make install命令
[root@linux lua-5.3.4]# make install
2. 6、最后進行測試,進到Linux的命令行,然后輸入lua命令,開始測試。
[root@linux lua-5.3.4]# lua >print('lua') lua
>print("lua") lua
2.7、按Ctrl+C退出lua命令模式。
>^C [root@linux lua-5.3.4]#
2.8、lua腳本文件名必須以.lua后綴名,如果在Linux命令行執行lua腳本,直接lua 腳本名稱。
[root@linux lua-5.3.4]# cd /root/application/program/ //執行文件都在這個目錄里面 [root@linux program]# mkdir luascript //創建luaScript腳本目錄,存放lua腳本文件 [root@linux program]# cd luascript [root@linux luascript]# lua 01.lua //執行01.lua腳本文件
2.9、redis與lua腳本結合使用,如果在lua腳本里使用了 redis.call命令來操作Redis,執行lua腳步如下面:
//redis-cli和lua腳本的路徑可以是相對路徑,也可以是絕對路徑 //以下代碼就是通過絕對地址來執行 //絕對地址: [root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/02.lua //相對地址: //當前目錄 192.168.127.128:6379>pwd [root@linux redis-tool]/root/application/program/redis-tool/ [root@linux redis-tool]# redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/02.lua
2.10、Redis客戶端執行帶有參數的lua腳本,腳本文件的名稱是:03.lua。
//當前redis 數據庫中只有name和age兩個key,其他數據已經清空。 //當前所在目錄 192.168.127.128:6379>keys * 1)"name" 2)"age" 192.168.127.128:6379>get name "liulei" 192.168.127.128:6379>get age "15" //03.lua腳本代碼如下: local name=redis.call("get",KEYS[1]) local age=redis.call("get",KEYS[2]) if name=="LLL" then redis.call("set",KEYS[1],ARGV[1]) redis.call("incr",KEYS[2]) end //執行改腳本的命令,必須在Linux的命令行,不是在Redis的命令行 [root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/03.lua name age , patrickLiu //執行腳本命令后 192.168.127.128:6379>keys * 1)"name" 2)"age" 192.168.127.128:6379>get name "patrickLiu" 192.168.127.128:6379>get age "16" //說明帶參數的執行Lua腳本成功
2.11、Redis客戶端執行有參數lua,並返回lua的表類型。
//04.lua文件的源碼 local b1=redis.call("hgetall",KEYS[1]) return b1 //代碼很簡單,話不多說 //清空當前數據庫 192.168.127.128:6379>flushdb 192.168.127.128:6379>keys * (empty list or set) 192.168.127.128:6379>hmset myhash name zhangsan sex nan address hebeibaoding school laiyuanyizhong OK 192.168.127.128:6379>hmget myhash name sex address school 1)"zhangsan" 2)"nan" 3)"hebeibaoding" 4)"laiyuanyizhong" //我們通過redis客戶端獲取myhash的結果,進入到redis客戶端的當前目錄 [root@linux redis-tool]# redis-cli -h 192.168.127.128 -p 6379 --eval ../luascript/04.lua myhash 1)"name" 2)"zhangsan" 3)"sex" 4)"nan" 5)"address" 6)"hebeibaoding" 7)"school" 8)"laiyuanyizhong" //成功獲取myhash的列表
六、總結
今天就寫到這里,還有很多內容要寫,一次也寫不完,慢慢來吧,我也需要消化一下。如果有時間,下一篇文章或者下某一篇文章准備來單獨寫一些關於Lua腳本語法的東西,我也是第一次接觸這個東西,大家一起學習吧。