首先,讓我們先回顧一下上一篇講的在Redis Functions中關於將key的名字作為引數和非key名字作為引數的區別,先看下面的例子。首先,我們先在一個Lua指令碼檔案mylib.lua中定義如下的庫和函數。
//--------------------mylib.lua 檔案開始 ----------- // #!lua name= mylib local function my_hset(keys, args) local hash = keys[1] local time = redis.call('TIME')[1] //這一行的目的是執行TIME命令得到當前的伺服器時間 return redis.call('HSET', hash, '_last_modified_', time, unpack(args)) end redis.register_function('my_hset', my_hset) //--------------------mylib.lua 檔案結束 ----------- //
然後我們在命令列中執行如下命令:
$ cat mylib.lua | redis-cli -x FUNCTION LOAD"mylib"
當我們再執行如下FCALL命令時候,我們會得到如下的結果:
redis> FCALL my_hset 1 myhash_key first_field "first value" second_field "second value"(integer) 3
上面命令的my_hset是函數名,1代表第一個引數myhash_key是key名,後面的first_field "first value" second_field "second value" 都是這個key: myhash_key的field:value對我們通過在Redis使用者端執行 HGETALL myhash_key可以檢視到相對應的結果:
redis> HGETALL myhash_key 1) "_last_modified_" 2) "1659536487" 3) "first_field" 4) "first value" 5) "second_field" 6) "second value"
好了,在複習完了上一篇Redis Functions的內容之後,我們可以看到上面的例子中mylib庫中只包含了一個函數my_hset, 事實上在一個庫中可以包括多個函數,在下面的例子中我們要新增另外2個function:my_hgetall 與 my_hlastmodified.其中,函數my_hgetall相當於在redis 使用者端執行hgetall命令,而my_hlastmodified函數返回的是傳入的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中註冊這兩個函數。
redis.register_function('my_hgetall', my_hgetall) redis.register_function('my_hlastmodified', my_hlastmodified)
因為我們要在mylib庫中新增兩個函數,所以我們需要執行如下的命令:
$ cat mylib.lua | redis-cli -x FUNCTION LOAD REPLACE"mylib"
接下來我們執行my_hgetall函數與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命令來檢視當前所有的Redis Function的庫及其包含的函數資訊。下面我們來檢視一下當前庫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的官方檔案中,我們發現我們可以在庫中定義一個function供庫中的其他函數使用,在官方的使用說明,它給出了一個例子用於錯誤檢測。我們下面來看看這個例子。定義一個check_keys的函數,用來檢測使用者端呼叫的redis function的函數輸入的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
相應的,我們也需要修改已經有的3個函數,讓他們去呼叫新增的check_keys函數
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
這裡要注意一點,在上面的例子中,如果我們執行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"
我們可以看到執行是正常的,但是如果我們執行FCALL_RO:
redis> FCALL_RO my_hgetall 1 myhash_key (error) ERR Can not execute a script with write flag using *_ro command.
我們會收到一個錯誤。這是為什麼呢?事實上,函數my_hgetall的目的只是從資料中讀取資料,並沒有寫操作。但是當我們定義它的時候,並沒有標註它是唯讀屬性
local function my_hgetall(keys, args) local hash = keys[1] local res = redis.call('HGETALL', hash) return res end
所以這個時候,當我們用命令FCALL_RO就會返回一個錯誤:Can not execute a script with write flag using *_ro command所以為了可以讓FCALL_RO執行一個唯讀屬性的函數,我們需要對my_hgetall的註冊方式進行一下修改:
redis.register_function{ function_name='my_hgetall', callback=my_hgetall, flags={ 'no-writes' } }
這個時候,我們再執行FCALL_RO命令時候,我們就會得到正確的結果了
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是如何在叢集中執行
在非叢集狀態時候,如果有主從節點,那麼主節點上的function會自動地被複制到從節點,這與function建立的初衷也是一致的:Redis認為Functions是資料庫的一部分,是可以持久化的,並且也是可以複製的。但是,如果function是存在於叢集的節點中,那麼就有一些不一樣的特性了。Redis function無法被自動地載入到叢集中的所有節點,而需要被管理員手動地將function載入到叢集中的每個節點中。要載入Redis function在叢集中的節點,可以執行如下的命令:
redis-cli --cluster-only-masters --cluster call host:port FUNCTION LOAD [REPLACE] function-code
如果執行命令:
redis-cli --cluster add-node
那麼這個時候叢集中已經存在的節點會將已經載入的function自動地傳播到新加入集訓的節點。
使用Redis Function的其他的注意事項
如果一個已經載入Redis Function的節點需要重啟,那麼在重啟之前建議先執行命令:
redis-cli --functions-rdb
這樣會將已經存在於節點上的Redis function存到一個RDB檔案中,當這個節點重啟之後可以直接載入這個rdb檔案,就不需要再手工載入每一個function了,大大地節省了時間和減少錯誤機率。本篇文章參照了部分Redis官方檔案的例子, 大家可以從連結去檢視和執行一下。
https://redis.io/docs/manual/programmability/functions-intro/
下一篇,我們開始分析Redis function的原始碼,讓我們深入探討一下這個Redis新特性內部是如何執行,歡迎繼續關注Redis Functions系列文章。
最後,感謝Redis社群的所有貢獻者的努力工作,及所有對Redis感興趣的開發者的支援。