1.redis的面臨問題
原子性問題
redis雖然是單一線程的,當時仍然會存在線程安全問題,當然,這個線程安全問題不是來源安于Redis服務器內部。而是Redis作為數據服務器,是提供給多個客戶端使用的。多個客戶端的操作就相當于同一個進程下的多個線程,如果多個客戶端之間沒有做好數據的同步策略,就會產生數據不一致的問題。舉個簡單的例子
多個客戶端的命令之間沒有做請求同步,導致實際執(zhí)行順序可能會不一致,最終的結果也就無法滿足原子性了。
效率問題
redis本身的吞吐量是非常高的,因為它首先是基于內存的數據庫。在實際使用過程中,有一個非常重要的因素影響redis的吞吐量,那就是網絡。我們在使用redis實現(xiàn)某些特定功能的時候,很可能需要多個命令或者多個數據類型的交互才能完成,那么這種多次網絡請求對性能影響比較大。當然redis也做了一些優(yōu)化,比如提供了pipeline管道操作,但是它有一定的局限性,就是執(zhí)行的多個命令和響應之間是不存在相互依賴關系的。所以我們需要一種機制能夠編寫一些具有業(yè)務邏輯的命令,減少網絡請求
2.lua
Redis中內嵌了對Lua環(huán)境的支持,允許開發(fā)者使用Lua語言編寫腳本傳到Redis中執(zhí)行,Redis客戶端可以使用Lua腳本,直接在服務端原子的執(zhí)行多個Redis命令
使用腳本的好處:
1.減少網絡開銷,在Lua腳本中可以把多個命令放在同一個腳本中運行
-
原子操作,redis會將整個腳本作為一個整體執(zhí)行,中間不會被其他命令插入。換句話說,編寫腳本的過程中無需擔心會出現(xiàn)競態(tài)條件
3.復用性,客戶端發(fā)送的腳本會永遠存儲在redis中,這意味著其他客戶端可以復用這一腳本來完成同樣的邏輯,Lua是一個高效的輕量級腳本語言(javascript、shell、sql、python、ruby…),用標準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能;
記下來來初步的認識一下在redis中如何結合lua來完成一些簡單的操作
3.在Lua腳本中調用Redis命令
在Lua腳本中調用Redis命令,可以使用redis.call函數調用。比如我們調用string類型的命令
redis.call(‘set’,’hello’,’world’)
local value=redis.call(‘get’,’hello’)
redis.call 函數的返回值就是redis命令的執(zhí)行結果。前面我們介紹過redis的5中類型的數據返回的
4.從Lua腳本中獲得返回值
在很多情況下我們都需要腳本可以有返回值,畢竟這個腳本也是一個我們所編寫的命令集,我們可以像調用其他redis內置命令一樣調用我們自己寫的腳本,所以同樣redis會自動將腳本返回值的Lua數據類型轉化為Redis的返回值類型。 在腳本中可以使用return 語句將值返回給redis客戶端,通過return語句來執(zhí)行,如果沒有執(zhí)行return,默認返回為ni
5.EVAL命令
[EVAL][腳本內容] [key參數的數量][key …] [arg …]
可以通過key和arg這兩個參數向腳本中傳遞數據,他們的值可以在腳本中分別使用KEYS和ARGV 這兩個類型的全局變量訪問。比如我們通過腳本實現(xiàn)一個set命令,通過在redis客戶端中調用,那么執(zhí)行的語句是:
lua腳本的內容為:
return redis.call(‘set’,KEYS[1],ARGV[1]) //KEYS和ARGV必須大寫
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 lua1 hello
注意:EVAL命令是根據 key參數的數量-也就是上面例子中的1來將后面所有參數分別存入腳本中KEYS和ARGV兩個表類型的全局變量。當腳本不需要任何參數時也不能省略這個參數。如果沒有參數則為0
6.EVALSHA命令
考慮到我們通過eval執(zhí)行l(wèi)ua腳本,腳本比較長的情況下,每次調用腳本都需要把整個腳本傳給redis,比較占用帶寬。為了解決這個問題,redis提供了EVALSHA命令允許開發(fā)者通過腳本內容的SHA1摘要來執(zhí)行腳本。該命令的用法和EVAL一樣,只不過是將腳本內容替換成腳本內容的SHA1摘要
Redis在執(zhí)行EVAL命令時會計算腳本的SHA1摘要并記錄在腳本緩存中
執(zhí)行EVALSHA命令時Redis會根據提供的摘要從腳本緩存中查找對應的腳本內容,如果找到了就執(zhí)行腳本,否則返回“NOSCRIPT No matching script,Please use EVAL”
通過以下案例來演示EVALSHA命令的效果
script load "return redis.call('get','lua1')" //將腳本加入緩存并生成sha1命令,
//結果為 "a5a402e90df3eaeca2ff03d56d99982e05cf6574"
evalsha "a5a402e90df3eaeca2ff03d56d99982e05cf6574" 0
我們在調用eval命令之前,先執(zhí)行evalsha命令,如果提示腳本不存在,則再調用eval