筆記來自
node使用redis-lua
Java使用redis-lua
兩種 Lua 腳本
真正的node和java使用筆記移步【node/redis】和【Java/redis】筆記
支持
Redis從2.6.0版本開始提供了eval命令,通過內置的Lua解釋器,可以讓用戶執行一段Lua腳本並返回數據,所以不需要本地安裝lualit服務
兩種腳本
- eval,可以使用 EVAL 命令對 Lua 腳本進行求值
# script:執行的腳本
# numkeys:指定鍵名參數個數
# key:鍵名,可以多個(key1、key2),通過 KEYS[1] KEYS[2] 的形式訪問
# atg:鍵值,可以多個(val1、val2),通過 ARGS[1] ARGS[2] 的形式訪問
EVAL script numkeys key [key ...] arg [arg ...]
# 通過 KEYS[] 數組的形式訪問 ARGV[],這里下標是以 1 開始,KEYS[1] 對應的鍵名為 name1,ARGV[2] 對應的值為 val2
EVAL "return redis.call('SET', KEYS[1], ARGV[2])" 2 name1 name2 val1 val2
# 通過 get 查看 name1 對應的值為 val2
get name1 // val2
- EVALSHA,EVAL 命令要求你在每次執行腳本的時候都發送一次腳本主體 (script body)。Redis 有一個內部的緩存機制,因此它不會每次都重新編譯腳本,通過 EVALSHA 來實現,根據給定的 SHA1 校驗碼,對緩存在服務器中的腳本進行求值。SHA1 怎么生成呢?通過 script 命令,可以對腳本緩存進行操作
SCRIPT FLUSH:清除所有腳本緩存
SCRIPT EXISTS:檢查指定的腳本是否存在於腳本緩存
SCRIPT LOAD:將一個腳本裝入腳本緩存,但並不立即運行它
SCRIPT KILL:殺死當前正在運行的腳本
# script 換成了 EVALSHA sha1
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
# 生成hash緩存
SCRIPT LOAD "redis.pcall('SET', KEYS[1], ARGV[2]);"
// "2a3b189808b36be907e26dab7ddcd8428dcd1bc8"
# 執行hash
EVALSHA 2a3b189808b36be907e26dab7ddcd8428dcd1bc8 2 name1 name2 val1 val2
# 通過 get 查看 name1 對應的值為 val2
get name1 // val2
內置了cjson
- 序列化
# 創建一個文件 RedisLuaCjsonEncode.lua
local userName = ARGV[1];
local userObject = {
name = userName,
age = 14,
address = 'China'
}
local userJson = cjson.encode(userObject);
if redis.call('set', KEYS[1], userJson) == 0
then
return -1
else
return userJson
end
# 不用連接redis,直接執行,英文的逗號,前后都要有空格
redis-cli --eval D:\redis\lua\RedisLuaCjsonEncode.lua name , value
- 反序列化
# 創建一個文件 RedisLuaCjsonEncode.lua
local userInfo = redis.call('get', KEYS[1])
local userJson = cjson.decode(userInfo)
return userJson.name;
# 不用連接redis,直接執行
redis-cli --eval D:\redis\lua\RedisLuaCjsonDecode.lua name
防止超賣
該場景下,如果設計方案中用戶搶紅包的行為不是放入隊列的, 而是簡單並發, 那么查詢redis計數器這一步操作,很可能就會在臨界點,例如已經被搶了9999個了,此時兩個用戶幾乎同時搶紅包,都查詢到還能繼續搶,最后發放了兩個紅包出去。當然,這還是樂觀的,實際情況可能是瞬間並發量非常大,導致發放了更多的紅包出去。
查詢計數器跟存儲搶到紅包的信息兩個操作是原子性的,redis為我們提供了multi事務,實際上也是無法做到的,multi只適合可以並行的操作,對於這種需要先執行A再能決定是否執行B的串行操作不適用。
此時lua就可以幫我們忙了,在lua里查詢,判斷,確定搶到紅包完全是一個原子操作,也不需要把搶紅包動作設計為一個隊列,更不需要去擔心並發的影響。
此外,兩個操作合並為一個lua腳本去執行時,還節省了一步redis的io耗時。
進一步講,redis緩存了所有執行過的lua腳本,只要設計得當,這個操作向redis傳送腳本的帶寬可以節省到一個sha碼的大小。
所以lua腳本對於redis來說可以是一把利刃
Redis使用Lua的好處
- 減少網絡開銷:本來5次網絡請求的操作,可以用一個請求完成,原先5次請求的邏輯放在redis服務器上完成。使用腳本,減少了網絡往返時延。
- 原子操作:Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。
- 復用:客戶端發送的腳本會永久存儲在Redis中,意味着其他客戶端可以復用這一腳本而不需要使用代碼完成同樣的邏輯。
Redis使用Lua的注意點
- Lua腳本的bug特別可怕,由於Redis的單線程特點,一旦Lua腳本出現不會返回(不是返回值)得問題,那么這個腳本就會阻塞整個redis實例。
- Lua腳本應該盡量短小實現關鍵步驟即可。(原因同上)
- Lua腳本中不應該出現常量Key,這樣會導致每次執行時都會在腳本字典中新建一個條目,應該使用全局變量數組KEYS和ARGV, KEYS和ARGV的索引都從1開始
- 傳遞給lua腳本的的鍵和參數:傳遞給lua腳本的鍵列表應該包括可能會讀取或者寫入的所有鍵。傳入全部的鍵使得在使用各種分片或者集群技術時,其他軟件可以在應用層檢查所有的數據是不是都在同一個分片里面。另外集群版redis也會對將要訪問的key進行檢查,如果不在同一個服務器里面,那么redis將會返回一個錯誤。(決定使用集群版之前應該考慮業務拆分),參數列表無所謂。。
- lua腳本跟單個redis命令和事務段一樣都是原子的已經進行了數據寫入的lua腳本將無法中斷,只能使用SHUTDOWN NOSAVE殺死Redis服務器,所以lua腳本一定要測試好。