初識Redis lua腳本

Lua腳本

Lua是一個高效的輕量級腳本語言,用標準C語言編寫並以源代碼形式開放, 其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。

Lua 腳本功能是 Reids 2.6 版本的最大亮點, 通過內嵌對Lua環境的支持, Redis 解決了長久以來不能高效地處理 CAS (check-and-set) 命令的缺點, 並且可以通過組合使用多個命令, 輕鬆實現以前很難實現或者不能高效實現的模式。

使用腳本的好處

  1. 減少網絡開銷,在Lua腳本中可以把多個命令放在同一個腳本中運行。
  2. 原子操作,Redis會將整個腳本作爲一個整體執行,中間不會被其他命令插入。換句話說,編寫腳本的過程中無需擔心會出現競態條件。
  3. 複用性,客戶端發送的腳本會永遠存儲在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 的值。

LuaRedis 類型之間存在一對一的轉換。 下表顯示了所有轉換規則

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 ... shaNSHA1摘要的list作爲參數,返回0或1的數組,表示爲該SHA1摘要的腳本是否存在。
  • SCRIPT LOAD script。此命令在Redis腳本緩存中註冊指定的腳本。 該命令在我們要確保EVALSHA不會失敗,而無需實際執行腳本(因爲之前的EVALSHA的腳本都是執行過EVAL之後緩存在腳本系統中的)。
  • SCRIPT KILL。該命令是中斷長時間運行的腳本(已達到配置的腳本最大執行時間)的唯一方法。 SCRIPT KILL命令只能用於在執行期間未修改數據集的腳本(因爲停止只讀腳本不會違反腳本引擎保證的原子性)。

其他 Lua腳本的資料請見 Redis官方文檔 Lua腳本

參看文獻

[1] Redis官方文檔 Lua腳本
[2] Redis使用之Lua腳本

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章