運行環境window,redis版本3.2.1。此處暫不對Lua進行詳細講解,只從Redis的方面講解。redis
在Redis的2.6版本推出了腳本功能,容許開發者使用Lua語言編寫腳本傳到Redis中執行,在Lua腳本中也能夠調用大部分的Redis命令。使用腳本有如下三個好處:數據庫
(1) 減小網絡開銷:有些時候須要屢次請求Redis獲取處理數據,而使用腳本功能就能夠只使用一次請求完成相同操做,減小了網絡往返時延。數組
(2) 原子操做:Redis會將整個腳本做爲一個總體執行,中間不會被其餘命令插入。也就是說在編寫腳本的過程當中無須擔憂會出現競態條件,也就是無須使用事務。事務能夠完成的全部功能,均可以用腳原本完成。緩存
(3) 複用:客戶端發送的腳本會永久存儲在Redis中,這就意味着其餘客戶端(能夠是其餘語言開發的項目)能夠複用這一腳本而不須要使用代碼完成一樣的邏輯。安全
一、EVAL命令服務器
編寫完腳本後最重要的就是在程序中執行腳本。Redis提供了EVAL命令可使開發者像調用其餘Redis內置命令同樣調用腳本。EVAL的命令格式以下:網絡
127.0.0.1:6379> eval script numkeys key [key ...] arg [arg ...]
script:腳本內容。numkeys:key參數的數量。key和arg:這兩個參數向腳本傳遞數據,它們的值能夠在腳本中分別使用KEYS[index]和ARGV[index]兩個表類型的全局變量訪問,numkeys爲key的數量和其索引的最大值,argv的索引爲key和argv數量總和減去numkeys,它們的索引都是從1開始,超出則返回nil。以下:dom
C:\Users\Xu>redis-cli 127.0.0.1:6379> eval 'return ARGV[3]' 2 key1 key2 value1 value2 value3 "value3" 127.0.0.1:6379> eval 'return KEYS[2]' 2 key1 key2 value1 value2 value3 "key2" 127.0.0.1:6379> eval 'return KEYS[3]' 2 key1 key2 value1 value2 value3 (nil)
其中要讀寫的鍵名應該爲key參數,其餘數據都做爲arg參數。函數
除了上面直接寫lua腳本,還能夠讀取lua腳本文件來執行腳本,命令以下:this
C:\Users\Xu>redis-cli --eval lua_file_path key1 key2 , arg1 arg2 arg3
注意不須要numkeys,逗號先後必須有空格,不然會被認爲一個連起來的字符串。
//lua文件內容 return ARGV[2] //執行命令 C:\Users\Xu>redis-cli.exe --eval e:\redis\a.lua key1 , value1 value2 "value2" C:\Users\Xu>redis-cli.exe --eval e:\redis\a.lua key1 , value1 value2,value3 "value2,value3"
二、EVALSHA命令
考慮到在腳本比較長的時候,若是每次調用腳本都須要將整個腳本傳給Redis會佔用較多的帶寬。因此,Redis提供了EVALSHA命令容許開發者經過腳本內容的SHA1摘要來執行腳本,該命令的用法和EVAL同樣,不過就是將腳本內容的script替換爲它的SHA1摘要。
Redis在執行EVAL命令時會計算腳本的SHA1摘要並記錄在腳本緩存中,若是執行EVALSHA命令時沒有從腳本緩存中找到相應的摘要,則返回錯誤。
127.0.0.1:6379> evalsha c349a436bd639369c62c971941fc5f7a80626ee6 1 key1 value1 (integer) 666 127.0.0.1:6379> evalsha c349a436bd639369c62c971941fc5f7a80626ee61 1 key1 value1 (error) NOSCRIPT No matching script. Please use EVAL.
在程序中使用EVALSHA的流程以下:
(1) 先計算腳本SHA1摘要,並使用EVALSHA執行。
(2) 得到返回值,若是返回錯誤則使用EVAL從新執行腳本。
三、SCRIPT LOAD命令
若是隻是想將腳本加入到腳本緩存中而不執行則則能夠用SCRIPT LOAD命令,返回值時腳本的SHA1摘要。
127.0.0.1:6379> script load 'return 666' "c349a436bd639369c62c971941fc5f7a80626ee6"
四、SCRIPT EXISTS命令
SCRIPT EXISTS命令能夠同時查找一個或者多個腳本的SHA1摘要是否已經本緩存,1爲存在0爲不存在。
127.0.0.1:6379> script exists c349a436bd639369c62c971941fc5f7a80626ee6 123ls436bd639369c62c971941fc5f7a80626ee6 1) (integer) 1 2) (integer) 0
五、SCRIPT FLUSH命令
Redis將腳本的SHA1摘要加入到腳本緩存後會永久保存,不會刪除,可是能夠用SCRIPT FLUSH刪除全部腳本緩存。
127.0.0.1:6379> script flush OK (1.51s)
六、SCRIPT KILL 和 SHUTDOWN NOSAVE
因爲Redis的腳本是原子性的,腳本執行期間不會執行其餘命令。爲了防止某個腳本執行時間過長致使Redis沒法提供服務(好比死循環),Redis提供了lua-time-limit參數限制腳本最長運行時間,默認是5秒。再腳本執行期間,執行其餘命令會返回「BUSY」錯誤,以下:
(A)127.0.0.1:6379> eval 'while true do end' 0
(B)127.0.0.1:6379> get foo (error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
此時Redis只會接受並執行兩個命令:SCRIPT KILL 和 SHUTDOWN NOSVAE。
經過SCRIPT KILL 能夠終止當前腳本的運行,腳本中止並返回錯誤:
(B)127.0.0.1:6379> script kill OK (B)127.0.0.1:6379> get foo (nil) (A)127.0.0.1:6379> eval 'while true do end' 0 (error) ERR Error running script (call to f_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f84e7): @user_script:1: Script killed by user with SCRIPT KILL... (175.99s)
若是當前執行的腳本對Redis的數據進行了修改,則SCRIPT KILL不會終止腳本的運行,由於這樣違背了原子性。那麼須要經過SHUTDOWN NOSAVE來強制終止Redis將原先腳本的修改操做返回,不進行持久化操做,這意味着全部發送在上一次的快照後的數據庫修改都會丟失。
不少狀況下,都須要腳本經過return返回值,若是沒有執行return則默認返回nil。由於咱們能夠像調用其餘Redis內置命令同樣調用咱們本身寫的腳本,因此一樣Redis會自動將腳本返回值的Lua數據類型轉化成Redis的返回值類型。具體的轉換規則以下:
(1) Lua的數字類型,Redis爲整數類型。
127.0.0.1:6379> eval 'return 1.1' 0 (integer) 1
(2) Lua的字符串類型,Redis也是字符串類型
(3) Lua的表類型(數組形式),Redis會返回多行字符串
127.0.0.1:6379> eval 'return {0,1}' 0 1) (integer) 0 2) (integer) 1
(4) Lua表類型(只有一個ok字段存儲狀態信息),Redis爲成功狀態回覆
127.0.0.1:6379> eval 'return {ok="this is ok"}' 0 this is ok
(5)Lua表類型(只有一個err字段存儲狀態信息),Redis爲錯誤狀態回覆
127.0.0.1:6379> eval 'return {err="so bad"}' 0 (error) so bad
(6)Lua的bool類型中true爲Redis的1,false爲nil
127.0.0.1:6379> eval 'return true' 0 (integer) 1 127.0.0.1:6379> eval 'return false' 0 (nil)
Redis腳本禁止使用Lua標準庫中與文件或系統調用相關的函數,在腳本中只容許對Redis的數據進行處理。而且Redis還經過禁用腳本的全局變量的方式保證每一個腳本都是相對隔離的,不會互相干擾。
使用沙盒不只是爲了保證服務器的安全性,並且還確保了腳本的執行結果只有和腳本自己和執行時傳遞的參數有關,不依賴外界條件(如系統時間、系統中某個文件的內容、其餘腳本執行結果登)。這是由於在執行復制和AOF持久話操做時記錄的腳本的內容而不是腳本調用的命令,因此必須保證在腳本內容和參數同樣的前提下腳本的執行結果必須同樣。
對於隨機數,Redis替換了math.random和math。randomseed函數使得每次執行腳本時生成的隨機數列都相同,若是但願得到不一樣的隨機數序列,最簡單的方法時由程序生成隨機數並經過參數傳遞給腳本,或者採用更靈活的方法,即在程序中生成隨機數傳給腳本做爲隨機數種子。
很簡單,直接上代碼,這裏舉例最基本的,還有不少的重寫方法你們能夠本身試試。最簡單的使用eval。
var script = " return KEYS[1];"; var keys = new RedisKey[]{ "key1","key2"}; var values = new RedisValue[] { "value1", "value2" }; return await redisConnection.GetDatabase().ScriptEvaluateAsync(script, keys, values);
緩存腳本,並使用。
var bytes = await redisConnection.GetServer(Config.Get("ConnectionStrings:Redis:ConnectionString")).ScriptLoadAsync("return 1"); var result = await redisConnection.GetDatabase().ScriptEvaluateAsync(bytes, null, null);
腳本是否已緩存。
bool exist = await redisConnection.GetServer(Config.Get("ConnectionStrings:Redis:ConnectionString")).ScriptExistsAsync("return 1");
刪除全部腳本緩存,這個操做須要鏈接的ConfigurationOptions配置中AllowAdmin = true,沒有會報錯哦。
redisConnection.GetServer(Config.Get("ConnectionStrings:Redis:ConnectionString")).ScriptFlush();
還有LuaScript和LoadedLuaScript兩個類能夠對腳本進行更多複雜的腳本,LuaScript將@myVar形式的腳本中的變量重寫爲redis所需的合適的ARGV[someIndex]。若是傳遞的參數是RedisKey類型,它將做爲KEYS集合的一部分自動發送。以下。
var lua = LuaScript.Prepare("return @key"); var result = redisConnection.GetDatabase().ScriptEvaluate(lua,new {key= (RedisKey)"key1",value = "value1" });