首先,讓我們先回顧一下上一篇講的在Redis Functions中關(guān)于將key的名字作為參數(shù)和非key名字作為參數(shù)的區(qū)別陶珠,先看下面的例子挟裂。
首先,我們先在一個Lua腳本文件mylib.lua中定義如下的庫和函數(shù)揍诽。
//--------------------mylib.lua 文件開始 ----------- //
#!lua name= mylib
local function my_hset(keys, args)
local hash = keys[1]
local time = redis.call('TIME')[1] //這一行的目的是執(zhí)行TIME命令得到當(dāng)前的服務(wù)器時間
return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
end
redis.register_function('my_hset', my_hset)
//--------------------mylib.lua 文件結(jié)束 ----------- //
然后我們在命令行中運(yùn)行如下命令:
$ cat mylib.lua | redis-cli -x FUNCTION LOAD"mylib"
當(dāng)我們再運(yùn)行如下FCALL命令時候诀蓉,我們會得到如下的結(jié)果:
redis> FCALL my_hset 1 myhash_key first_field "first value" second_field "second value"(integer) 3
上面命令的my_hset是函數(shù)名,1代表第一個參數(shù)myhash_key是key名暑脆,后面的first_field "first value" second_field "second value" 都是這個key: myhash_key的field:value對
我們通過在Redis客戶端運(yùn)行 HGETALL myhash_key可以查看到相對應(yīng)的結(jié)果:
redis> HGETALL myhash_key
1) "_last_modified_"
2) "1659536487"
3) "first_field"
4) "first value"
5) "second_field"
6) "second value"
好了渠啤,在復(fù)習(xí)完了上一篇Redis Functions的內(nèi)容之后,我們可以看到上面的例子中mylib庫中只包含了一個函數(shù)my_hset, 事實(shí)上在一個庫中可以包括多個函數(shù)添吗,在下面的例子中我們要添加另外2個function:my_hgetall 與 my_hlastmodified.
其中沥曹,函數(shù)my_hgetall相當(dāng)于在redis 客戶端執(zhí)行hgetall命令,而my_hlastmodified函數(shù)返回的是傳入的key中_last_modified這一個field的值碟联。
首先妓美,讓我們在mylib.lua中添加這兩個function
local function my_hgetall(keys, args)
local hash = keys[1]
local res = redis.call('HGETALL', hash)
return res
end
local function my_hlastmodified(keys, args)
local hash = keys[1]
return redis.call('HGET', keys[1], '_last_modified_')
end
然后,在mylib中注冊這兩個函數(shù)鲤孵。
redis.register_function('my_hgetall', my_hgetall)
redis.register_function('my_hlastmodified', my_hlastmodified)
因?yàn)槲覀円趍ylib庫中新增兩個函數(shù)壶栋,所以我們需要運(yùn)行如下的命令:
$ cat mylib.lua | redis-cli -x FUNCTION LOAD REPLACE"mylib"
接下來我們運(yùn)行my_hgetall函數(shù)與my_hlastmodified
redis> FCALL my_hgetall 1 myhash_key
1) "_last_modified_"
2) "1659536487"
3) "first_field"
4) "first value"
5) "second_field"
6) "second value"
redis> FCALL my_hlastmodified 1 myhash_key
"1659536487"
最后我們可以通過FUNCTION LIST命令來查看當(dāng)前所有的Redis Function的庫及其包含的函數(shù)信息。下面我們來查看一下當(dāng)前庫mylib中包括的3個function的信息普监。
127.0.0.1:6381> FUNCTION list
1) 1) "library_name"
2) "mylib"
3) "engine"
4) "LUA"
5) "functions"
6) 1) 1) "name"
2) "my_hset"
3) "description"
4) (nil)
5) "flags"
6) (empty array)
2) 1) "name"
2) "my_hlastmodified"
3) "description"
4) (nil)
5) "flags"
6) (empty array)
3) 1) "name"
2) "my_hgetall"
3) "description"
4) (nil)
5) "flags"
6) (empty array)
在Redis的官方文檔中贵试,我們發(fā)現(xiàn)我們可以在庫中定義一個function供庫中的其他函數(shù)使用,在官方的使用說明凯正,它給出了一個例子用于錯誤檢測毙玻。我們下面來看看這個例子。
定義一個check_keys的函數(shù)廊散,用來檢測客戶端調(diào)用的redis function的函數(shù)輸入的key是否存在并且只有一個淆珊。
local function check_keys(keys)
local error = nil
local nkeys = table.getn(keys)
if nkeys == 0 then
error = 'Hash key name not provided'
elseif nkeys > 1 then
error = 'Only one key name is allowed'
end
if error ~= nil then
redis.log(redis.LOG_WARNING, error);
return redis.error_reply(error)
end
return nil
end
相應(yīng)的,我們也需要修改已經(jīng)有的3個函數(shù)奸汇,讓他們?nèi)フ{(diào)用新增的check_keys函數(shù)施符。
local function my_hset(keys, args)
local error = check_keys(keys)
if error ~= nil then
return error
end
local hash = keys[1]
local time = redis.call('TIME')[1]
return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
end
local function my_hgetall(keys, args)
local error = check_keys(keys)
if error ~= nil then
return error
end
local hash = keys[1]
local res = redis.call('HGETALL', hash)
return res
end
local function my_hlastmodified(keys, args)
local error = check_keys(keys)
if error ~= nil then
return error
end
local hash = keys[1]
return redis.call('HGET', keys[1], '_last_modified_')
end
這里要注意一點(diǎn)往声,在上面的例子中,如果我們運(yùn)行FCALL:
redis> FCALL my_hgetall 1 myhash_key
1) "_last_modified_"
2) "1659536487"
3) "first_field"
4) "first value"
5) "second_field"
6) "second value"
我們可以看到運(yùn)行是正常的戳吝,但是如果我們運(yùn)行FCALL_RO:
redis> FCALL_RO my_hgetall 1 myhash_key
(error) ERR Can not execute a script with write flag using *_ro command.
我們會收到一個錯誤浩销。這是為什么呢?
事實(shí)上听哭,函數(shù)my_hgetall的目的只是從數(shù)據(jù)中讀取數(shù)據(jù)慢洋,并沒有寫操作。但是當(dāng)我們定義它的時候陆盘,并沒有標(biāo)注它是只讀屬性普筹。
local function my_hgetall(keys, args)
local hash = keys[1]
local res = redis.call('HGETALL', hash)
return res
end
所以這個時候,當(dāng)我們用命令FCALL_RO就會返回一個錯誤:Can not execute a script with write flag using *_ro command
所以為了可以讓FCALL_RO執(zhí)行一個只讀屬性的函數(shù)隘马,我們需要對my_hgetall的注冊方式進(jìn)行一下修改:
redis.register_function{
function_name='my_hgetall',
callback=my_hgetall,
flags={ 'no-writes' }
}
這個時候太防,我們再運(yùn)行FCALL_RO命令時候,我們就會得到正確的結(jié)果了
redis> FCALL_RO my_hgetall 1 myhash_key
1) "_last_modified_"
2) "1659536487"
3) "first_field"
4) "first value"
5) "second_field"
6) "second value
Redis functions是如何在集群中運(yùn)行
在非集群狀態(tài)時候酸员,如果有主從節(jié)點(diǎn)蜒车,那么主節(jié)點(diǎn)上的function會自動地被復(fù)制到從節(jié)點(diǎn),這與function創(chuàng)建的初衷也是一致的:Redis認(rèn)為Functions是數(shù)據(jù)庫的一部分幔嗦,是可以持久化的酿愧,并且也是可以復(fù)制的。
但是邀泉,如果function是存在于集群的節(jié)點(diǎn)中嬉挡,那么就有一些不一樣的特性了。Redis function無法被自動地加載到集群中的所有節(jié)點(diǎn)汇恤,而需要被管理員手動地將function加載到集群中的每個節(jié)點(diǎn)中棘伴。要加載Redis function在集群中的節(jié)點(diǎn),可以運(yùn)行如下的命令:
redis-cli --cluster-only-masters --cluster call host:port FUNCTION LOAD [REPLACE] function-code
如果運(yùn)行命令:
redis-cli --cluster add-node
那么這個時候集群中已經(jīng)存在的節(jié)點(diǎn)會將已經(jīng)加載的function自動地傳播到新加入集訓(xùn)的節(jié)點(diǎn)屁置。
使用Redis Function的其他的注意事項(xiàng)
如果一個已經(jīng)加載Redis Function的節(jié)點(diǎn)需要重啟焊夸,那么在重啟之前建議先運(yùn)行命令:
redis-cli --functions-rdb
這樣會將已經(jīng)存在于節(jié)點(diǎn)上的Redis function存到一個RDB文件中,當(dāng)這個節(jié)點(diǎn)重啟之后可以直接加載這個rdb文件蓝角,就不需要再手工加載每一個function了阱穗,大大地節(jié)省了時間和減少錯誤機(jī)率。
本篇文章引用了部分Redis官方文檔的例子使鹅, 大家可以從鏈接去查看和運(yùn)行一下揪阶。
https://redis.io/docs/manual/programmability/functions-intro/
下一篇,我們開始分析Redis function的源碼患朱,讓我們深入探討一下這個Redis新特性內(nèi)部是如何執(zhí)行鲁僚,歡迎繼續(xù)關(guān)注Redis Functions系列文章。
最后,感謝Redis社區(qū)的所有貢獻(xiàn)者的努力工作冰沙,及所有對Redis感興趣的開發(fā)者的支持侨艾。