一、為什麼要使用Lua腳本的好處
1、減少網絡開銷:可以将多個請求通過腳本的形式一次發送,減少網絡時延和請求次數。
2、原子性的操作:Redis會将整個腳本作為一個整體執行,中間不會被其他指令插入。是以在編寫腳本的過程中無需擔心會出現競态條件,無需使用事務。
3、代碼複用:用戶端發送的腳步會永久存在redis中,這樣,其他用戶端可以複用這一腳本來完成相同的邏輯。
4、速度快:見 與其它語言的性能比較, 還有一個 JIT編譯器可以顯著地提高多數任務的性能; 對于那些仍然對性能不滿意的人, 可以把關鍵部分使用C實作, 然後與其內建, 這樣還可以享受其它方面的好處。
5、可以移植:隻要是有ANSI C 編譯器的平台都可以編譯,你可以看到它可以在幾乎所有的平台上運作:從 Windows 到Linux,同樣Mac平台也沒問題, 再到移動平台、遊戲主機,甚至浏覽器也可以完美使用 (翻譯成JavaScript).
6、源碼小巧:20000行C代碼,可以編譯進182K的可執行檔案,加載快,運作快。
二、redis本地用戶端調用示例
基本介紹
EVAL 和 EVALSHA 指令是從 Redis 2.6.0 版本開始的,使用内置的 Lua 解釋器,可以對 Lua 腳本進行求值。
在lua腳本中使用redis.call與redis.pcall調用redis指令。
redis.pcall函數,功能與redis.call相同,唯一的差別是當redis指令執行出錯時,redis.pcall會記錄錯誤并繼續執行,而redis.call會直接傳回錯誤,不會繼續執行。
在腳本中可以使用return語句将值傳回給用戶端,如果沒有執行return語句則預設傳回。
redis Eval 指令基本文法如下:
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
參數說明:
script: 參數是一段 Lua 5.1 腳本程式。腳本不必(也不應該)定義為一個 Lua 函數。
numkeys: 用于指定鍵名參數的個數。
key [key ...]: 從 EVAL 的第三個參數開始算起,表示在腳本中所用到的那些 Redis 鍵(key),這些鍵名參數可以在 Lua 中通過全局變量 KEYS 數組,用 1 為基址的形式通路( KEYS[1] , KEYS[2] ,以此類推)。
arg [arg ...]: 附加參數,在 Lua 中通過全局變量 ARGV 數組通路,通路的形式和 KEYS 變量類似( ARGV[1] 、 ARGV[2] ,諸如此類)。
執行個體
xxx>eval "return redis.call('set',KEYS[1],ARGV[1])" 1name ling
OK
xxx>get name"ling"
在redis中編寫lua腳本時,可以通過KEYS與ARGV來傳入外部參數,每個KEY都是通過KEYS表指定。ARGV表用來傳遞參數,這個例子中ARGV用來傳入name的值。
關于EVALSHA指令
EVAL 指令要求你在每次執行腳本的時候都發送一次腳本主體(script body)。Redis 有一個内部的緩存機制,是以它不會每次都重新編譯腳本,不過在很多場合,付出無謂的帶寬來傳送腳本主體并不是最佳選擇。
為了減少帶寬的消耗, Redis 實作了 EVALSHA 指令,它的作用和 EVAL 一樣,都用于對腳本求值,但它接受的第一個參數不是腳本,而是腳本的 SHA1 校驗和(sum)。
它會根據給定的 sha1 校驗碼,執行緩存在伺服器中的腳本。
三、什麼時候使用Lua
三個主要優點:原子性,減少網絡io,速度快。
關于WATCH/MULTI/EXEC與lua的差別與選擇
Redis支援WATCH/MULTI/EXEC這樣的塊,能進行一組操作,也能一起送出執行,看起來與Lua有重疊。應該如何進行選擇?
MULT塊中所有操作獨立,但在Lua中,後面的操作能依賴前面操作的執行結果。同時使用Lua腳本還能夠避免WATCH使用後競争條件引起用戶端反應變慢的情況。
在RedisGreen(譯注:國外一家專門提供Redis主機的服務商),我們看到許多應用使用Lua的同時也使用MULTI/EXEC,但兩者但不是替代關系。許多成功的Lua腳本都很小,僅僅實作一個你的應用需要而Redis指令中沒有單一的功能。
四、通路庫
Redis的Lua解釋器加載七個庫:base,table,string, math, debug,cjson和cmsgpack。前幾個都是标準庫,充許你使用任何語言進行基本的操作。後面兩個可以讓Redis支援JSON和MessagePack—這是非常有用的功能,同時我也很想知道為什麼常常看不到這種用法。
Web應用程式常常使用JSON作為api傳回資料,你也許也可以把一堆JSON資料存到Redis的key中。當想通路某些JSON資料時,首先需要儲存到一個hash中,使用Redis的JSON支援将非常友善:
if redis.call("EXISTS", KEYS[1]) == 1then
local payload= redis.call("GET", KEYS[1])return cjson.decode(payload)[ARGV[1]]else
returnnil
end
在這裡我們檢檢視key是否存在,如不存在則快速傳回nil。如存在則從Redis中擷取JSON值,用cjson.decode()進行解析,然後傳回請求内容。
redis-cli set apple '{ "color": "red", "type": "fruit" }'
=>OK
redis-cli eval "$(cat json-get.lua)" 1apple type=> "fruit"
加載這段腳本進你的Redis伺服器,将JSON資料儲存到Redis中,通常是hash。 雖然我們每次通路時都必須解析,但隻要你的對象很小,這個操作實際上是非常快的。
如果你的API隻是在内部提供,通常需要考慮效率上的問題,MessagePack 是比采用JSON更好的選擇,它更小,更快,在Redis(更多場合也是如此),MessagePack是JSON更好的替代品。
if redis.call("EXISTS", KEYS[1]) == 1then
local payload= redis.call("GET", KEYS[1])return cmsgpack.unpack(payload)[ARGV[1]]else
returnnil
end
五、Redis與lua資料類型轉換
當 Lua 通過 call() 或 pcall() 函數執行 Redis 指令的時候,指令的傳回值會被轉換成 Lua 資料結構。
同樣地,當 Lua 腳本在 Redis 内置的解釋器裡運作時,Lua 腳本的傳回值也會被轉換成 Redis 協定(protocol),然後由 EVAL 将值傳回給用戶端。
資料類型之間的轉換遵循這樣一個設計原則:如果将一個 Redis 值轉換成 Lua 值,之後再将轉換所得的 Lua 值轉換回 Redis 值,那麼這個轉換所得的 Redis 值應該和最初時的 Redis 值一樣。
換句話說, Lua 類型和 Redis 類型之間存在着一一對應的轉換關系。
redis傳回值類型和Lua資料類型轉換規則
redis傳回值類型Lua資料類型
整數回複 數字類型
字元串回複 字元串類型
多行字元串回複 table類型(數組形式)
狀态回複 table類型(隻有一個ok字段存儲狀态資訊)
錯誤回複 table類型(隻有一個err字段存儲錯誤資訊)
Lua資料類型和redis傳回值類型轉換規則
Lua資料類型redis傳回值類型
數字類型 整數回複(Lua的數字類型會被自動轉換成整數)
字元串類型 字元串回複
table類型(數組形式) 多行字元串回複
table類型(隻有一個ok字段存儲狀态資訊) 狀态回複
table類型(隻有一個err字段存儲錯誤資訊) 錯誤回複
Python 示例
pool = redis.ConnectionPool(host='xxx',port=6379, decode_responses=True)
conn= redis.Redis(connection_pool=pool)defluatest():
lua1= """
"""script2=conn.register_script(lua1)
script2(keys=[],args=[])
程式設計中一些注意的地方:
1.lua腳本中調用redis指令使用call與pcall指令。
2.Wrong number of args calling Redis command From Lua script此報錯為使用redis調用指令的使用參數不完整,比如有些操作hash的指令,需要兩個key,掉了一個key就會出現此錯誤。
3.lua腳本中支援多傳回值,lua中使用table存儲,傳回時直接傳回此table,python接收為一個list的。
總結:
下面這些都是在Redis中使用Lua時常見的錯誤:
表是Lua中的表達式,與很多流行語言不同。KEYS中的第一個元素是KEYS[1],第二個是KEYS[2](譯注:不是0開始)
nil是表的結束符,[1,2,nil,3]将自動變為[1,2],是以在表中不要使用nil。
redis.call會觸發Lua中的異常,redis.pcall将自動捕獲所有能檢測到的錯誤并以表的形式傳回錯誤内容。
Lua數字都将被轉換為整數,發給Redis的小數點會丢失,傳回前把它們轉換成字元串類型。
確定在Lua中使用的所有KEY都在KEY表中,否則在将來的Redis版中你的腳本都有不能被很好支援的危險。
Lua腳本和其它Redis操作一樣,在腳本執行時,其它的一切都不能運作。考慮用腳本來護展Redis伺服器能力,但要保持短小和有用。