Redis 7.x 系列【32】函数(Redis Functions)

有道无术,术尚可求,有术无道,止于术。

本系列Redis 版本 7.2.5

源码地址:https://gitee.com/pearl-organization/study-redis-demo

1. 概述

官方文档

EVAL 执行 LUA 脚本存在的问题:

  • 脚本可以被缓存,但是会存在丢失问题,例如重启服务器或者故障转移至副本时,客户端运行时需要重新加载
  • 客户端应用程序实例必须维护所有脚本
  • 在事务中调用缓存的脚本会增加事务失败的概率,因为可能缺少脚本
  • SHA1 散列值是无意义的,这使得在 MONITOR 会话中调试系统变得极为困难
  • 使用 EVAL 时,往往会促使客户端应用程序直接使用脚本而不负责使用 Lua API 中的 KEYSARGV,这是一种反模式。

Redis v7.0 引入了 Redis Functions (函数),是一种基于脚本的优化方案,提供与脚本相同的核心功能。通过持久化和复制保证可用性,不依赖于客户端,允许通过服务端加载自定义的函数库,并且会进行主从复制和持久化存储。支持多种语言,目前仅支持 LUA ,后续计划支持 JavaScriptPython 等。

2. 常用命令

2.1 FUNCTION LOAD

将一个库及函数加载到Redis中。

语法格式:

FUNCTION LOAD [REPLACE] function-code

参数说明:

  • REPLACE:加载已存在名称的库时,Redis 服务器将返回错误,使用 REPLACE 可以强制进行覆盖
  • function-code:必填参数,即函数源代码

注意以下情况下会返回错误:

  • 提供了无效的引擎名称。
  • 在没有 REPLACE 修饰符的情况下,已存在该库名称。
  • 在另一个库中已存在同名函数(即使指定了REPLACE)。
  • 引擎在创建库函数时失败(例如由于编译错误)。
  • 未声明任何函数。

示例:

redis> FUNCTION LOAD "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
mylib
redis> FCALL myfunc 0 hello
"hello"

2.2 FUNCTION DELETE

删除一个库及其所有函数。如果该库不存在,则服务器会返回错误。

语法格式:

FUNCTION DELETE library-name

2.3 FUNCTION LIST

返回有关函数和库的信息。

语法格式:

FUNCTION LIST [LIBRARYNAME library-name-pattern] [WITHCODE]

可选参数说明:

  • LIBRARYNAME:指定匹配库名称的模式
  • WITHCODE:包含库的源代码

响应信息:

  • library_name:库的名称。
  • engine:库的引擎。
  • functions:库中函数的列表。
  • 每个函数具有以下字段:
    • name:函数的名称。
    • description:函数的描述。
    • flags:函数标志的数组。
    • library_code:库的源代码(当使用WITHCODE修饰符时)。

示例:

localhost:0>FUNCTION LIST
 1)    1)   "library_name"
  2)   "mylib"
  3)   "engine"
  4)   "LUA"
  5)   "functions"
  6)      1)        1)     "name"
    2)     "knockknock"
    3)     "description"
    4)     null
    5)     "flags"
    6)    

2.4 FUNCTION RESTORE

恢复已被序列化的库。

语法格式:

FUNCTION RESTORE serialized-value [FLUSH | APPEND | REPLACE]

指定处理现有库的策略:

  • APPEND:将恢复的库追加到现有库中,并在冲突时中止。这是默认策略。
  • FLUSH:在恢复载荷之前删除所有现有库。
  • REPLACE:将恢复的库追加到现有库中,在名称冲突时替换现有库。请注意,此策略不会阻止函数名称的冲突,仅处理库名称冲突。

2.5 FUNCTION STATS

返回当前正在运行的函数以及可用执行引擎的信息。可以使用此命令检查长时间运行函数的调用,并决定是否使用 FUNCTION KILL 命令终止它。

语法格式:

FUNCTION STATS

返回键值对:

  • running_script:关于正在运行脚本的信息。如果没有正在运行的函数,服务器将返回 nil
  • name:函数的名称。
  • command:调用函数时使用的命令和参数。
  • duration_ms:函数的运行时长,以毫秒为单位。
  • engines:包含有关引擎的统计信息,如函数数量和库数量。

2.6 FUNCTION DUMP

返回已加载库的序列化内容,可以稍后使用 FUNCTION RESTORE 命令来恢复这些序列化的库。

语法格式:

FUNCTION DUMP	

示例,使用 FUNCTION DUMP 命令转储已加载的库,然后调用 FUNCTION FLUSH 删除所有库。接着使用FUNCTION RESTORE 从序列化内容中恢复原始库:

redis> FUNCTION LOAD "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
"mylib"
redis> FUNCTION DUMP
"\xf5\xc3@X@]\x1f#!lua name=mylib \n redis.registe\rr_function('my@\x0b\x02', @\x06`\x12\nkeys, args) 6\x03turn`\x0c\a[1] end)\x0c\x00\xba\x98\xc2\xa2\x13\x0e$\a"
redis> FUNCTION FLUSH
OK
redis> FUNCTION RESTORE "\xf5\xc3@X@]\x1f#!lua name=mylib \n redis.registe\rr_function('my@\x0b\x02', @\x06`\x12\nkeys, args) 6\x03turn`\x0c\a[1] end)\x0c\x00\xba\x98\xc2\xa2\x13\x0e$\a"
OK
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)

2.7 FUNCTION FLUSH

删除所有库。

语法格式:

FUNCTION FLUSH [ASYNC | SYNC]

有效的模式包括:

  • ASYNC:异步地清空库。
  • SYNC:同步地清空库。

2.8 FUNCTION KILL

终止当前正在执行的函数。只能用于在执行过程中没有修改数据集的函数(因为终止只读函数不会违反脚本引擎的原子性保证)。

语法格式:

FUNCTION KILL

2.9 FCALL

调用函数。

语法格式:

FCALL function numkeys [key [key ...]] [arg [arg ...]]

参数说明:

  • function:第一个参数是已加载函数的名称。
  • numkeyskey 参数的数量。
  • [key [key ...]] [arg [arg ...]]:和 EVAL 命令一下,指定多个 key 或参数。

示例:

redis> FUNCTION LOAD "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
"mylib"
redis> FCALL myfunc 0 hello
"hello"
RESP2/RESP3 Reply

3. 案例演示

3.1 定义库

每个 Redis 函数都属于一个加载到 Redis 中的库,首先需要定义一个库,格式为:

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

各部分解释如下:

  • #!:固定格式
  • <engine name>:使用的引擎,加载时会编译和评估库中的源代码,目前仅支持 lua
  • <library name>:库的名称

示例,定义一个 lua 引擎加载,名称为 mylib 的库:

#!lua name=mylib\n

每个库必须至少包含一个注册的函数才能成功加载,直接加载一个不包含函数的空库,会报错:

localhost:0>FUNCTION LOAD "#!lua name=mylib\n"
"ERR Invalid library metadata"

3.2 定义函数

定义好了库之后,可以在库中定义函数,格式为:

#!lua name=mylib

local function fun_name(keys, args)
  return xx
end

各部分解释如下:

  • fun_name:自定义的函数名称
  • keyskey 数组
  • args:参数数据
  • return:返回
  • end:函数结束标识

示例,获取某个字符串类型的值:

#!lua name=mylib

local function getV(keys)
  local key = keys[1] 
  return redis.call('GET', key) 
end

3.3 注册函数

定义好库和函数之后,还需要调用 redis.register_function() 来注册函数,这个 API 需要两个参数:

  • 注册的函数名
  • 回调函数

示例,直接使用匿名函数:

#!lua name=mylib
redis.register_function(
  'knockknock',
  function() return 'Who\'s there?' end
)

示例,外部定义函数:

#!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)

3.4 加载库和函数

定义好了库和函数后,需要加载到 Redis 服务器端,加载完成后会返回当前库名称。

使用 FUNCTION LOAD 命令进行加载:

127.0.0.1:6379> FUNCTION LOAD "#!lua name=mylib\nredis.register_function('knockknock', function() return 'Who\\'s there?' end)"
"mylib"

已存在当前库时,会报错:

(error) ERR Library 'mylib' already exists

也可以使用客户端 API 加载,Lettuce 示例:

        // 创建客户端
        RedisClient redisClient = RedisClient.create("redis://:123456@127.0.0.1:6379/0");
        // 获取连接
        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            // 执行命令
            RedisCommands<String, String> sync = connection.sync();
            String msg = sync.functionLoad("#!lua name=mylib\nredis.register_function('knockknock', function() return 'Who\\'s there?' end)");
            System.out.println(msg);

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            redisClient.shutdown();
        }

3.5 调用函数

可以使用 FCALL 调用函数,语法格式:

FCALL function numkeys [key [key ...]] [arg [arg ...]]

示例:

127.0.0.1:6379> FCALL knockknock 0
"Who's there?"

也可以使用客户端 API 调用,Lettuce 示例:

String res = sync.fcall("knockknock", ScriptOutputType.VALUE);
System.out.println(res);

4. 集群环境

在集群部署环境中,某个主节点的函数会自动向副本传播,但是还需要函数加载到其他所有集群节点上。这一过程集群不会自动处理,需要由集群管理员来完成,例如模块加载、配置设置等。

可以是使用下面的命令在所有主节点上执行加载函数:

redis-cli --cluster-only-masters --cluster call host:port FUNCTION LOAD

在添加节点时,redis-cli --cluster add-node 命令会自动处理将,已加载的函数从现有节点传播到新节点。

5. 迁移、备份还原

可以使用 redis-cli --functions-rdb 命令将当前服务端的函数进行备份,生成一个 RDB 文件,使用该文件可以将函数进行迁移、备份还原。

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云烟成雨TD

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值