Redis從2.6版本開始引入對Lua腳本的支持,通過在服務器中嵌入Lua環境,Redis客戶端可以使用Lua腳本,直接在服務端原子的執行多個Redis命令。
lua腳本的好處:
減少網絡開銷。可以將多個請求通過腳本的形式一次發送,減少網絡時延
原子操作。redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
復用。客戶端發送的腳步會永久存在redis中,這樣,其他客戶端可以復用這一腳本而不需要使用代碼完成相同的邏輯。
1.在redis里使用EVAL和EVALSHA
可以使用EVAL命令對輸入的腳本進行接受結果
如: Eval "return 1+1" 0
——>(integer)2
而使用evalsha 命令則可以根據腳本的SHA1校驗和對接受腳本的結果,但這個命令要求校驗和對應的腳本必須至少執行過一次或者這個校驗和對應的腳本被scriptload加載過。
如:
script load "return 1+2" // 會返回一個sha校驗
->"e13c398af9f2658ef7050acf3b266f87cXXXXXX"
->evalsha "e13c398af9f2658ef7050acf3b266f87cXXXXXX" 0
->(integer) 3
在腳本比較長的情況下,如果每次調用腳本都需要將整個腳本傳給Redis會占用較多的帶寬。為了解決這個問題,Redis提供了EVALSHA命令,允許開發者通過腳本內容的SHA1摘要來執行腳本,該命令的用法和EVAL一樣,只不過是將腳本內容替換成腳本內容的SHA1摘要。
2.偽客戶端
因為執行Redis命令必須有相應的客戶端狀態,所以為了執行Lua腳本中包含的Redis命令,Redis服務器專門為Lua環境創建了一個偽客戶端,並由這個偽客戶端負責處理Lua腳本中包含的所有Redis命令。
Lua腳本使用redis.call函數或者redis.pcall函數執行一個Redis命令,需要完成以下步驟:
(1).Lua環境將redis.call函數或者redis.pcall函數想要執行的命令傳給偽客戶端。
(2).偽客戶端將腳本想要執行的命令傳給命令執行器。
(3).命令執行器執行偽客戶端傳給它的命令,並將命令的執行結果返回給偽客戶端。
(4).偽客戶端接收命令執行器返回的命令結果,並將這個命令結果返回給Lua環境。
(5).Lua環境在接收到命令結果以后,將該結果返回給redis.call函數或者redis.pcall函數。
(6).接收到結果的redis.call函數或者redis.pcall函數會將命令結果作為函數返回值給腳本中的調用者。
3.lua_scripts字典
除了偽客戶端之外,Redis服務器偽Lua環境創建了另一個協作組件是lua_scripts字典,這個字典的鍵為某個Lua腳本的SHA1校驗和(checksum),而字典的值則是SHA1校驗和對應的Lua腳本:
如:
script load "retrun 1+1"
-> "e13c398af9f2658ef7050acf3b266f87cXXXXXX"
這個返回值和載入的腳本是一一對應關系,這個校驗值會存在script字典里。服務器會將所有被eval命令執行過的lua腳本,以及被scriptload載入過的腳本保存在字典里。
lua_script字典的兩個作用
(1).一個是實現script exists命令
(2).實現腳本復制功能。
4.EVAL命令的實現
EVAL命令的執行過程可以分為以下三個步驟:
(1)根據客戶端給定的Lua腳本,在Lua環境中定義一個Lua函數。
(2)將客戶端給定的腳本保存到lua_scripts字典,等待將來進一步說明。
(3)執行剛剛才Lua環境中定義的函數,以此來執行客戶端給定的Lua腳本。
5.Redis管理Lua腳本
(1).script load
此命令用於將Lua腳本加載到Redis內存中
(2).script exists
scripts exists sha1 [sha1 …]
此命令用於判斷sha1是否已經加載到Redis內存中
(3).script flush
此命令用於清除Redis內存已經加載的所有Lua腳本,在執行script flush后,sha1不復存在。
(4).script kill
此命令用於殺掉正在執行的Lua腳本。
6.redis.call和pcall
6.redsi編寫lua腳本
語法:
$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
--eval,告訴redis-cli讀取並運行后面的lua腳本
path/to/redis.lua,是lua腳本的位置
KEYS[1] KEYS[2],是要操作的鍵,可以指定多個,在lua腳本中通過KEYS[1], KEYS[2]獲取
ARGV[1] ARGV[2],參數,在lua腳本中通過ARGV[1], ARGV[2]獲取。
注意:
KEYS和ARGV中間的 ',' 兩邊的空格,不能省略。
如:
redis.call('set', 'foo', 'bar')
local value=redis.call('get', 'foo') --value的值為bar
redis.pcall函數,功能與redis.call相同,唯一的區別是當命令執行出錯時,redis.pcall會記錄錯誤並繼續執行,而redis.call會直接返回錯誤,不會繼續執行。
