Lua腳本
Lua
是一個高效的輕量級腳本語言,用標準C語言編寫並以源代碼形式開放, 其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。
Lua
腳本功能是 Reids 2.6
版本的最大亮點, 通過內嵌對Lua
環境的支持, Redis
解決了長久以來不能高效地處理 CAS (check-and-set)
命令的缺點, 並且可以通過組合使用多個命令, 輕鬆實現以前很難實現或者不能高效實現的模式。
使用腳本的好處
- 減少網絡開銷,在
Lua
腳本中可以把多個命令放在同一個腳本中運行。 - 原子操作,
Redis
會將整個腳本作爲一個整體執行,中間不會被其他命令插入。換句話說,編寫腳本的過程中無需擔心會出現競態條件。 - 複用性,客戶端發送的腳本會永遠存儲在
Redis
中,這意味着其他客戶端可以複用這一腳本來完成同樣的邏輯。
[EVAL] [腳本內容] [key參數的數量] [key …] [arg …]
在 Lua
腳本中使用全局變量 KEYS[i]
來獲取第幾個key
。在 Lua
腳本中使用全局變量 ARGV[i]
來獲取第幾個參數。例如:
# 返回結果是一個數組。
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
在 Lua
腳本中可以調用 Redis
命令。使用如下兩個函數來實現:
redis.call()
。如果redis.call()
調用命令產生error,redis.call()
將會引發Lua error
並強制EVAL返回1個error 給 命令的調用者。redis.pcall()
。與redis.call()
相似,不同之處在於,會捕獲error最後返回代表error的Lua table
。redis.error_reply(error_string)
返回一個error回覆。redis.status_reply(status_string)
返回一個status回覆。
> eval "return redis.call('set','foo','bar')" 0
OK
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
如上所示,使用下邊的 Lua
腳本而不是上面的那個,key
應當通過 KEYS
全局變量來獲取,而不是直接寫死在 Lua
腳本中。爲什麼這樣要求呢?
key
通過 KEYS
全局變量顯示傳遞,方便 Redis
分析傳遞的命令,這在確保 Redis Cluster
可以將請求轉發到適當的羣集節點上很有用。
Lua和Redis數據類型之間的轉換
當 Lua
使用 call()
或 pcall()
調用 Redis
命令時,Redis
返回值將轉換爲 Lua
數據類型。 同樣,在調用 Redis
命令和 Lua
腳本返回值時,Lua
數據類型將轉換爲 Redis
協議,以便腳本可以控制 EVAL
返回給 client
的值。
Lua
和 Redis
類型之間存在一對一的轉換。 下表顯示了所有轉換規則
Redis 到 Lua
- Redis integer reply -> Lua number
- Redis bulk reply -> Lua string
- Redis multi bulk reply -> Lua table (may have other Redis data types nested)
- Redis status reply -> Lua table with a single ok field containing the status
- Redis error reply -> Lua table with a single err field containing the error
- Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua 到 Redis
- Lua number -> Redis integer reply (the number is converted into an integer)
- Lua string -> Redis bulk reply
- Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
- Lua table with a single ok field -> Redis status reply
- Lua table with a single err field -> Redis error reply
- Lua boolean false -> Redis Nil bulk reply.
- Lua boolean true -> Redis integer reply with value of 1
示例:
> eval "return {1,2,3.3333,somekey='somevalue','foo',nil,'bar'}" 0
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"
Lua
中沒有浮點數,要表示浮點數則用字符串。在 Lua
轉換成 Redis
協議過程中遇到 nil
則停止轉換。
Lua腳本的原子性
Redis
保證Lua
腳本以原子性方式執行:當一個 Lua
腳本執行時,其他 Lua
腳本或者命令不會被執行, 所有的命令必須等待腳本執行完以後才能執行。從語義上來看,這類似於 Redis
事務中的MULTI
/EXEC
命令。爲了防止某個腳本執行時間過程導致 Redis
無法提供服務。Redis
提供了 lua-time-limit
參數限制腳本的最長運行時間。默認是5秒鐘。
當腳本運行時間超過這個限制後,Redis
將開始接受其他命令但不會執行(以確保腳本的原子性),而是返回 BUSY
的錯誤。
錯誤處理
當調用 redis.call()
導致一個 Redis
命令error時,會停止執行腳本並且返回一個error,表明這個error來自腳本。
> del foo
(integer) 1
> lpush foo a
(integer) 1
> eval "return redis.call('get','foo')" 0
(error) ERR Error running script (call to f_6b1bf486c81ceb7edf3c093f4c48582e38c0e791): ERR Operation against a key holding the wrong kind of value
使用 redis.pcall()
不會引發錯誤,但是會以上面指定的格式(作爲帶有 err
字段的 Lua
表)返回錯誤對象。 該腳本可以通過返回redis.pcall()
返回的錯誤對象,將確切的錯誤傳遞給用戶。
帶寬和EVALSHA
EVAL
命令強制您一次又一次發送腳本內容。 Redis
不需要每次都重新編譯腳本,因爲它使用內部緩存機制,在這種情況下使用EVAL
會佔用不必要的帶寬。爲了解決這些問題 Redis
提供了 EVALSHA
。
EVALSHA
的工作原理與 EVAL
完全相同,但是它沒有腳本作爲第一個參數,而是具有腳本的SHA1摘要
。 該行爲如下:
- 如果服務器仍然記住具有匹配的
SHA1摘要
的腳本,則將執行該腳本。 - 如果服務器不記得帶有此
SHA1摘要
的腳本,則會返回一個特殊錯誤,告訴客戶端改爲使用EVAL
。
> set foo bar
OK
> eval "return redis.call('get','foo')" 0
"bar"
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
> evalsha ffffffffffffffffffffffffffffffffffffffff 0
(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).
腳本命令
Redis
有幾個腳本命令。
SCRIPT FLUSH
。此命令是強制Redis
刷新腳本緩存的唯一方法。 它在可以將同一實例重新分配給其他用戶最有用(因爲緩存的是其他用戶的Lua腳本
,所以要flush
)。 這對於測試客戶端庫的腳本功能實現也很有用。SCRIPT EXISTS sha1 sha2 ... shaN
。SHA1
摘要的list作爲參數,返回0或1的數組,表示爲該SHA1
摘要的腳本是否存在。SCRIPT LOAD script
。此命令在Redis腳本緩存中註冊指定的腳本。 該命令在我們要確保EVALSHA不會失敗,而無需實際執行腳本(因爲之前的EVALSHA的腳本都是執行過EVAL之後緩存在腳本系統中的)。SCRIPT KILL
。該命令是中斷長時間運行的腳本(已達到配置的腳本最大執行時間)的唯一方法。SCRIPT KILL
命令只能用於在執行期間未修改數據集的腳本(因爲停止只讀腳本不會違反腳本引擎保證的原子性)。
其他 Lua腳本
的資料請見 Redis官方文檔 Lua腳本
參看文獻
[1] Redis官方文檔 Lua腳本
[2] Redis使用之Lua腳本