Redis中使用Lua指令碼的開發思路
Redis提供了通過eval命令來執行Lua指令碼。下面通過幾個小例子來講述如何在Redis服務端執行Lua指令碼。
1. 執行Lua指令碼的幾個命令如下:
命令格式 |
說明 |
對應Jedis用戶端Jedis對象的方法之一(有更多重載方法) |
EVAL script numkeys key [key ...] arg [arg ...] |
執行Lua指令碼 |
public Object eval(String script, int keyCount, String... params) |
EVALSHA sha1 numkeys key [key ...] arg [arg ...] |
根據給定的 sha1 校正碼,對緩衝在伺服器中的指令碼進行求值 |
public Object evalsha(String sha1, int keyCount, String... params) |
SCRIPT LOAD script |
將給定的指令碼緩衝,不執行,並返回sha1校正值 |
public String scriptLoad(String script) |
SCRIPT EXISTS sha1 [sha1 ...] |
給定一個或多個指令碼的 SHA1 校正和,返回一個包含 0 和 1 的列表,表示校正和所指定的指令碼是否已經被儲存在緩衝當中 |
public List<Boolean> scriptExists(String... sha1) |
SCRIPT FLUSH |
清除所有 Lua 指令碼緩衝 |
|
SCRIPT KILL |
殺死當前正在啟動並執行 Lua 指令碼,若且唯若這個指令碼沒有執行過任何寫操作時,這個命令才生效(如果已經執行了寫操作,則需要通過shutdown nosave命令來處理) |
2.通過redis-cli用戶端執行Lua指令碼
redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3
需要注意的是用逗號來分割key和參數,這裡與在互動式模式下執行evel命令有所不同。
3.實際案例
情境一:對一個特定請求1秒鐘只允許訪問10次,當符合請求訪問條件時,返回True,否則返回False。
Java用戶端操作Redis服務,實現代碼如下:
/**
* 存取控制
*
* 1秒內最多可訪問10次
*
* @param key
* @return
*/
public boolean isAccess(String key) {
String rkey = "acc:" + key;
long value = jedis.incr(rkey);
if (value == 1) {
jedis.expire(rkey, 1);
return true;
} else {
boolean rs = value <= 10;
return rs;
}
}
INCR命令作為計數器,如果rkey存在,則增加1返回最終值,否則初始化值為0,然後加1。如上程式,如果訪問rkey不存在,則表示第一次請求,這時對其rkey設定到期時間為1秒,否則比較其值是否超過制定請求數的閥值10.
用Lua指令碼來完成這一操作:
--[[
Judge status
KEYS[1]:key
ARGV[1]:request numbers
ARGV[2]:expires times seconds
--]]
local key, rqn, exp = KEYS[1], ARGV[1], ARGV[2];
local value=redis.call("incr", key);
redis.log(redis.LOG_NOTICE, "incr "..key);
if(tonumber(value) == 1)then
redis.call("expire", key, exp);
redis.log(redis.LOG_NOTICE, "expire "..key.." "..exp)
return true;
else
return tonumber(value) <= tonumber(rqn);
end
通過Java用戶端代碼實現該功能存在一定缺陷,比如每1秒就需要操作1個incr和expire命令,並且該命令是由用戶端通過網路發起的,而使用Lua指令碼則既可以保證操作的原子性,又能使每次操作只需要一個key即可在伺服器端完成相應的判斷操作。可以通過SCRIPT LOAD的方式將指令碼緩衝到伺服器,通過sha1校正值+參數(Key,ARG)來執行,減輕網路傳輸,也對該功能做到較好的封裝。
情境二:指定模式key大量刪除
redis目前提供的刪除命令del僅支援刪除指定數量的key,並不能通過指定模式key來進行刪除,比如:del *user 刪除以user結尾的key。
在redis中提供了keys命令,該命令可以通過指定模式key來擷取key列表,下面通過keys和del命令組合實現一個指定模式key大量刪除的命令。
--[[
Pattern delete key
KEYS[1]:pattern
--]]
redis.log(redis.LOG_NOTICE, "call keys "..KEYS[1]);
local keys=redis.call("keys", KEYS[1]);
local count = 0;
if(keys and (table.maxn(keys) > 0)) then
for index, key in ipairs(keys) do
redis.log(redis.LOG_NOTICE, "del "..key);
count = count + redis.call("del", key);
end
end
return count;
需要注意的是情境二可以作為一種思路,通過Lua指令碼組合redis內建命令來實現特定功能的命令。而這裡的模式key大量刪除並未一個好的命令,因為如果key的數量很大時,將會有比較嚴重的效能問題。redis預設限制Lua指令碼執行時間最大為5秒,如果超過5秒將繼續接受來自用戶端的請求,並簡單的返回BUSY結果。這時候則需要SCRIPT KILL或者SHUTDOWN NOSAVE命令做相應的處理。因此應該儘力保證指令碼的執行速度極快。
情境三:產生隨機數
對於Redis而且,指令碼執行在相同資料集,相同參數下執行寫命令具有一致性的。其不依賴與隱式的資料集,指令碼執行過程中不同執行時期的狀態變化,也不依賴外部I/O裝置的輸入。
要符合Redis服務執行的指令碼條件,需要注意的地方比較多,可以參見: http://redis.io/commands/eval
下面是實現隨機數列表的Lua指令碼:
--[[
Random lpush a list key-value
KEYS[1]:key name
ARGV[1]:ramdom seed value
ARGV[2]:add element count
--]]
math.randomseed(ARGV[1]);
for i=1, ARGV[2], 1 do
redis.call("lpush", KEYS[1], math.random());
end
redis.log(redis.LOG_NOTICE, "lpush " .. KEYS[1]);
return true;
上述指令碼通過改變randomseed函數的參數來實現隨機數,如果兩次執行上述指令碼,ARGV[1]參數值相同,則產生的隨機數是相同的。
通過執行上述指令碼,記錄每次生產的值,然後刪除對應key,再次產生。
對比上述結果,在執行該指令碼時,隨機數的產生由seed參數(第一個參數)決定的。
相同隨機數種子下產生的隨機數是相同的,如果再次執行指令碼,指定產生的隨機數個數n小於已經產生的隨機數個數m,則取已經產生的前n個,如果指定產生的隨機數個數n大於已經產生的隨機數個數m,則次數再產生(n-m)個隨機數,並固定下來。
4.Redis中使用Lua指令碼總結
Redis內建了Lua解譯器,這為操作Redis伺服器和資料提供了巨大的靈活性。
文中幾個情境並不見得實際,有效,但並不能掩蓋Lua與Redis結合將為Redis的使用提供了更大的想象和操作空間。
我們可以通過Lua來實現更多特定功能的命令;用Lua來封裝複雜了Redis操作的業務;計數,統計,分析,收集資料;實現業務操作事務控制等等。更多情境,還需在實際中不斷摸索和嘗試。
Ubuntu 14.04下Redis安裝及簡單測試
Redis叢集明細文檔
Ubuntu 12.10下安裝Redis(圖文詳解)+ Jedis串連Redis
Redis系列-安裝部署維護篇
CentOS 6.3安裝Redis
Redis安裝部署學習筆記
Redis設定檔redis.conf 詳解
Redis 的詳細介紹:請點這裡
Redis 的:請點這裡
本文永久更新連結地址: