Redis Functions 介紹之一

2023-11-06 18:00:32

Redis提供了程式設計介面(programming interface)可以讓你在Redis伺服器端執行客戶的指令碼。

一個重大的變化就是從Redis 7開始,你可以選擇使用Redis Functions去管理和執行你的指令碼,而在此之前你只能使用EVAL命令執行Lua指令碼。

通過EVAL命令執行的指令碼是有缺陷的。如果在Redis伺服器端執行了命令SCRIPT FLUSH, 或者伺服器重啟,或者主節點執行了一個主備切換,那麼存在於伺服器端的指令碼將會丟失,於是使用者端的應用程式需要重新將整個的指令碼再次傳送到伺服器。這個缺陷實際上說明,客戶要執行的指令碼需要使用者端的應用程式去維護而不是Redis伺服器端維護。所以,為了解決指令碼的一系列問題,Redis在最新發布的7.0版本中提出了Functions這個概念。

Redis Functions最重要的2點是可以持久化的,並且也是可以複製的。Redis Functions提供了和指令碼一樣的核心功能,但是Redis認為Functions是資料庫的一部分,因此,使用者端的應用程式在執行時候不需要再load它們,也不用擔心在執行事務的操作時候會有中斷的危險。在使用Functions之前僅需要先宣告它們(declare before use),這樣,使用者端應用程式僅僅需要呼叫Functions的API即可,而不需要再關注那些在指令碼中的程式邏輯了。Functions提供的這些豐富的API可以包括很多Redis的核心命令,這一點非常 類似於Redis的Modules, 並且,Redis的Functions可以達到開發一次,使用多次的目的,同一個Functions可以在多個使用者端應用程式中重複使用。

每個Function都有一個唯一的名字,並且每個Function都屬於一個唯一的庫(library),在一個庫中可以包含多個Function. 要注意一點的是,這個庫的內容是不可以改變的,不可以選擇性地改變或者更新它所包含的Function,而是必須將整個庫作為一個操作將所有的庫一起更新。這個特性使得可以在一個庫中的多個Function可以互相呼叫,或者共用程式碼。

另外要注意的一點,Function的執行是原子化的。當一個Function在執行它的程式碼時候,它會阻塞Redis Server執行其他命令,因此,Functions的程式碼應執行的非常快,儘量避免在Functions中出現執行時間比較長的程式碼段。

下面我們以範例說明如何使用Redis Functions

我們前面已經說過,每一個Function都屬於一個唯一的庫(library)。載入(Loading)一個庫進Redis資料庫需要通過FUNCTION LOAD命令。這個命令將庫的payload作為輸入,這個輸入的格式如下:

#!<engine name> name=<library name>

讓我們看一個例子:

下面的例子是建立了一個library名字是mylib, 並且這個庫有一個函數名字為myfunc, 這個函數的目的是返回它的第一個引數

例子1:

redis> FUNCTION LOAD "#!lua name=mylib 
                        redis.register_function('myfunc', function(keys, args) 
                                                 return args[1] end)"
mylib ---- 命令返回值

這個命令的返回值是載入的庫的名字,這裡就是mylib 我們可以通過FCALL命令呼叫在庫mylib中註冊的函數myfuncredis> FCALL myfunc 0 hello"hello" 你們可以看到,返回值就是hello

例子2:此外還有2個與FUNCTION LOAD相關的命令,FUNCTION LIST 與 FUNCTION DELETE

redis> function list
1) 1) "library_name"
   2) "mylib"
   3) "engine"
   4) "LUA"
   5) "functions"
   6) 1) 1) "name"
         2) "myfunc"
         3) "description"
         4) (nil)
         5) "flags"
         6) (empty array

例子3:

redis> FUNCTION DELETE mylib
OK
redis> FCALL myfunc 0
(error) ERR Function not found

以上3個命令的詳細資訊與如何具體的使用可以檢視下面的連結:

https://redis.io/commands/function-load/

https://redis.io/commands/function-list/

https://redis.io/commands/function-delete/ 

你們可能已經注意到了,上面呼叫庫中函數的命令是:redis> FCALL myfunc 0 hello"hello" 在其中包括myfunc函數名和一個數位0. 這個數位表示後面的鍵的數目(the number of key names that follow it),在這個例子中,不包括任何的key,所以這裡寫做了0我們下面要講解一個關於在Redis Functions中關於將key的名字作為引數和非key名字作為引數的區別 為了讓Redis Functions能夠正確的執行,在function中所有的要存取的key的名字都必須明確地(explicitly)將這些key的名字作為輸入引數;任何不是key名字的引數都將被視為普通的輸入引數下面我們將會舉一個例子進行說明:一個HSET的資料型別,我們想為每個這種資料型別的key存貯如下資訊:

HGETALL myhashkey
1) "_last_modified_"
2) "1654705366"
3) "orange"
4) "good"
5) "apple"
6) "perfect"
7) "banada"
8) "very good

除了3—8的貨品資訊,還包括一個1-2的最新的修改資訊 我們可以先在一個Lua指令碼檔案mylib.lua中定義如下的庫和函數

#!lua name=mylib

local function my_hset(keys, args)
  local hash = keys[1]
  local time = redis.call('TIME')[1]
  return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
end

redis.register_function('my_hset', my_hset)

然後我們在命令列中執行如下命令:

$ cat mylib.lua | redis-cli -x FUNCTION LOAD REPLACE
"mylib"

然後當我們再執行如下FCALL命令時候,我們會得到如下的結果:

redis> FCALL my_hset 1 myhash orange "good" apple "perfect" banada "very good"
(integer) 3

上面命令的my_hset是函數名,1代表第一個引數myhash是key名,後面的orange "good" apple "perfect" banada "very good" 都是這個key:myhash的field:value對 

好了,這篇文章就先介紹到這裡,下次我們將接續介紹Redis Functions的其他高階特點。