redis執行lua腳本的實現方法
從redis 2.6.0版本開始,redis內置瞭Lua解釋器,並提供瞭eval命令來解析Lua腳本求值。
1. 語法格式
語法: eval script numkeys keys args
參數: eval — redis提供解析lua腳本的命令
script — lua腳本
numkeys — 指定鍵名參數集(keys)的個數
keys — 鍵名參數集,通過全局變量KEYS數組表示,起始下標為1
args — 鍵值參數集,通過全局變量ARGV數組表示,起始下標為1
描述: EVAL命令的語義要求字面量不要直接寫在lua腳本中,推薦使用變量來定義lua腳本,並將字面量放在鍵名參數集keys和鍵值參數集args中,通過全局變量KEYS和ARGV來獲取,這樣做的好處是可緩存!在lua腳本中,可以使用兩個函數來執行redis命令,分別是:redis.call()和redis.pcall()
例子:
## 第一個eval命令,設置一個key=name,value=sym的字符串 eval "return redis.call('set',KEYS[1],ARGA[1])" 1 name sym ## 第二個eval命令:獲取key=name的字符串的值 eval "return redis.call('get',KEYS[1])" 1 name
錯誤處理:
上面說過,在lua腳本中可以使用call()和pcall()來執行redis腳本,這兩個函數的效果是一模一樣的,唯一區別就是它們對於錯誤處理的不同:
①redis.call()在執行命令中發生錯誤,腳本會停止執行,返回一個腳本錯誤,錯誤的輸出信息會說明錯誤造成的原因:
②redis.pcall()執行命令出錯時將捕獲錯誤並返回表示錯誤的Lua表類型
2.類型轉換
當 Lua 通過 call() 或 pcall() 函數執行 Redis 命令的時候,命令的返回值會被轉換成 Lua 數據結構;同樣地,當Lua腳本在 redis內置解釋器裡運行時,Lua的返回值也會被轉換成Redis類型,然後由EVAL將值返回客戶端。lua類型與redis類型之間存在一一轉換的關系:
redis -> lua
redis類型 |
lua類型 |
描述 |
redis_integer |
lua_number |
redis整數轉為lua數字 |
redis_bulk |
lua_string |
redis bulk回復轉為lua字符串 |
redis_multi bulk |
Lua_table |
redis 多條bulk回復轉為Lua 表 |
redis_status |
lua_table |
redis狀態回復轉為lua表,表內ok域包含狀態信息 |
redis_error |
lua_table |
redis錯誤回復轉為lua表,表內的err域包含錯誤信息 |
redis_nil、 redis_multi nil |
lua_boolean_false |
redis的nil回復和nil多條回復轉為lua的佈爾值false |
lua -> redis
lua類型 |
redis類型 |
描述 |
lua_number |
redis_integer |
lua數字轉為redis整數 |
lua_string |
redis_bluk |
lua字符串轉為redis bulk回復 |
lua_table、 lua_array |
redis_multi bulk |
lua表(數組)轉為redis多條bulk回復 |
lua_table_ok |
redis status |
一個帶單個ok域的lua表,轉為redis 狀態回復 |
lua_table_err |
redis_error |
一個帶單個err域的lua表,轉為redis 錯誤回復 |
lua_boolean_false |
redis nil |
lua佈爾值false轉為redis的nil回復 |
從lua轉換到redis有一條額外的規則,這條規則沒有與其相對應的redis轉換為lua的規則:
lua_boolean_true -> redis_integer_1,lua佈爾值true轉為redis整數1
3.lua腳本
3.1 script命令
redis提供瞭以下幾個script命令,用於對於腳本子系統進行控制:
script flush:清除所有的腳本緩存
script load:將腳本裝入腳本緩存,不立即運行並返回其校驗和
script exists:根據指定腳本校驗和,檢查腳本是否存在於緩存
script kill:殺死當前正在運行的腳本(防止腳本運行緩存,占用內存)
3.2 腳本原子性
redis使用單個lua解釋器去運行所有腳本,並且保證腳本會以原子性的方式去執行,意味著當某個腳本在運行時,不會有其它腳本或者redis命令被執行!所以,如果當前腳本運行很慢,服務器可能會因為正忙而無法執行命令,如:
每個腳本都有一個最大執行時間限制,默認值是5s。最大執行時間的長短由配置文件redis.conf的lua-time-limit選項來控制,或直接使用config get和config set命令來修改。當一個腳本執行達到最大執行時間,redis不會主動結束它,它會進行下面幾個步驟:
①redis記錄一個腳本正在超時運行
②redis開始重新接受其它客戶端請求,但隻接受執行script kill命令和shutdown nosave兩個命令,若客戶端執行其它命令,redis會返回busy錯誤。
③如果腳本隻執行過讀操作,使用script kill命令可以立即停止此腳本;如果腳本執行過寫操作,隻允許shutdown save/nosave命令,通過停止服務器來阻止當前數據寫入磁盤。(此時服務器關閉,數據不會被保存)
3.3 腳本緩存和EVALSHA
redis有一個內部的腳本緩存機制,它不會每次都重新編譯腳本,反倒是它會將所有運行過的腳本永久保存在腳本緩存中(因為redis發現腳本體積非常小,即使量很大,甚至經常修改,儲存這些腳本的內存也是微不足道的)。清空腳本緩存隻有唯一一個方式,就是執行script flush命令。使用eval命令執行腳本時,每次都要發送腳本主體,如果腳本足夠復雜,這會付出無謂的網絡帶寬。redis基於對lua的緩存,它實現瞭evalsha命令。
evalsha命令和eval命令效果一樣,都是解釋lua腳本執行,但是evalsha命令的第一個參數不是腳本主體,而是腳本的SHAI校驗和,這個校驗和可以通過script load命令得到。evalsha命令執行過程分兩步:
①如果redis服務器保存瞭給定SHA1校驗和所指定的腳本,就會執行該腳本
②如果redis服務器沒保存給定SHA1校驗和所指定的腳本,它就會返回一個特殊錯誤,告知客戶端使用eval命令去執行
如下圖所示:
3.4 全局變量保護
redis的lua腳本不允許創建全局變量,如果腳本需要在多次執行之間維持某種狀態,可以借助外部redis key來保存狀態,每次腳本執行前,獲取redis相對應的key賦值給局部變量。在lua腳本中創建或訪問一個全局變量,都會引起腳本停止,eval命令會返回一個錯誤:
redis的全局變量保護並不是百分百成功,有時候會在腳本中混入lua全局狀態,可能會引發AOF持久化和主從復制都無法得到保證。redis建議不要在腳本中使用全局變量,可以使用local關鍵字定義腳本中的變量!
3.5 日志記錄
在redis中使用腳本不會自動記錄日志,需要我們在腳本使用redis.log()手動保存日志信息。腳本保存的日志,隻有那些與redis實例所設置的日志等級相同或更高級才會被記錄。語法:redis.log(loglevel,message)。其中,message表示要記錄的日志信息,是一個字符串;loglevel表示redis日志等級,有4個取值:
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
到此這篇關於redis執行lua腳本的實現方法的文章就介紹到這瞭,更多相關redis執行lua內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Redis中Lua腳本的使用和設置超時
- redis中lua腳本使用教程
- 用python 批量操作redis數據庫
- redis通過lua腳本,獲取滿足key pattern的所有值方式
- redis lua腳本實戰秒殺和減庫存的實現