Redis Functions 介紹之二

2023-11-10 18:00:26

首先,讓我們先回顧一下上一篇講的在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感興趣的開發者的支援。