一、為什么要使用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])" 1 name 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]) == 1 then local payload = redis.call("GET", KEYS[1]) return cjson.decode(payload)[ARGV[1]] else return nil end
在這里我們檢查看key是否存在,如不存在則快速返回nil。如存在則從Redis中獲取JSON值,用cjson.decode()進行解析,然后返回請求內容。
redis-cli set apple '{ "color": "red", "type": "fruit" }' => OK redis-cli eval "$(cat json-get.lua)" 1 apple type => "fruit"
加載這段腳本進你的Redis服務器,將JSON數據保存到Redis中,通常是hash。 雖然我們每次訪問時都必須解析,但只要你的對象很小,這個操作實際上是非常快的。
如果你的API只是在內部提供,通常需要考慮效率上的問題,MessagePack 是比采用JSON更好的選擇,它更小,更快,在Redis(更多場合也是如此),MessagePack是JSON更好的替代品。
if redis.call("EXISTS", KEYS[1]) == 1 then local payload = redis.call("GET", KEYS[1]) return cmsgpack.unpack(payload)[ARGV[1]] else return nil 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) def luatest(): 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)
總結:
下面這些都是在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服務器能力,但要保持短小和有用。