Redis Functions 介绍之一

发布时间 2023-11-06 15:18:17作者: 中间件小哥

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的其他高级特点。