在Redis中,是否可以调用其他Lua脚本中定义的Lua函数?

4

我曾试图在声明函数时未使用local关键字,然后从另一个脚本中调用该函数,但在运行命令时出现错误。

test = function ()    
    return 'test'
end



# from some other script
test()

编辑:

我简直不敢相信我还没有得到答案。我将包含更多关于我的设置的细节。

我正在使用Node,并使用redis-scripto包将脚本加载到redis中。这里是一个例子。

var Scripto = require('redis-scripto');
var scriptManager = new Scripto(redis);

scriptManager.loadFromDir('./lua_scripts');

var keys    = [key1, key2];
var values  = [val];

scriptManager.run('run_function', keys, values, function(err, result) {
console.log(err, result)
})

还有Lua脚本。

-- ./lua_scripts/dict_2_bulk.lua

-- turns a dictionary table into a bulk reply table
dict2bulk = function (dict)
    local result = {}
    for k, v in pairs(dict) do
        table.insert(result, k)
        table.insert(result, v)
    end
    return result
end


-- run_function.lua 

return dict2bulk({ test=1 })

抛出以下错误。
[Error: ERR Error running script (call to f_d06f7fd783cc537d535ec59228a18f70fccde663): @enable_strict_lua:14: user_script:1: Script attempted to access unexisting global variable 'dict2bulk' ] undefined

我会问一个显而易见的问题:在第二个脚本中,你是否(或其他任何地方)“require”指定函数的脚本?也就是说,那段代码实际上是否运行? - Nathan
脚本通过“script load”命令加载到Redis中。脚本本身内部没有任何要求。我需要它们吗? - Tim Fairbrother
如果我使用 require,它会给我一个错误。脚本尝试访问不存在的全局变量 'require'。我不确定 require 是否能够在 Redis 中工作。 - Tim Fairbrother
@Nathan,请问当Redis没有文件系统时,我应该如何在其中使用require? - Tim Fairbrother
抱歉,我没有意识到 Redis 不允许使用 require。但只要调用创建函数的脚本,您的代码应该可以正常工作。您确认过了吗? - Nathan
显示剩余3条评论
3个回答

10

与被接受的答案相反,因为被接受的答案是错误的。

虽然您无法明确定义命名函数,但您可以使用EVALSHA调用任何脚本。 更具体地说,您通过SCRIPT LOAD或隐式通过EVAL定义的所有Lua脚本都在全局Lua名称空间中可用,格式为 f_<sha1哈希值>(直到/除非您调用SCRIPT FLUSH),您可以随时调用它们。

遇到的问题是函数被定义为不接受任何参数,并且KEYSARGV表实际上是全局的。因此,如果要在Lua脚本之间通信,则需要操纵您的KEYSARGV表,或者需要使用标准Redis键空间进行函数之间的通信。

127.0.0.1:6379> script load "return {KEYS[1], ARGV[1]}"
"d006f1a90249474274c76f5be725b8f5804a346b"
127.0.0.1:6379> eval "return f_d006f1a90249474274c76f5be725b8f5804a346b()" 1 "hello" "world"
1) "hello"
2) "world"
127.0.0.1:6379> eval "KEYS[1] = 'blah!'; return f_d006f1a90249474274c76f5be725b8f5804a346b()" 1 "hello" "world"
1) "blah!"
2) "world"
127.0.0.1:6379>

所有这些都是完全违反规范的,并且在尝试在Redis集群场景中运行时可能以奇怪的方式停止工作。


嗨,乔赛亚,好消息。完全违反规范 有点让我担心,但我会考虑一下。这并不让我感到惊讶,因为它完全没有记录。当然,除了Redis源代码本身。 - Tw Bert
那么,诀窍在于规范没有详细说明底层的实际操作,只是一些你应该或不应该做的事情。尽管它自2.4分支以来与几年前的脚本编写相同,但它没有提到函数是否可用,参数存储在哪里等等。 - Josiah

2

重要提示:请参考下面Josiah的回答。我的回答结果错误不完整,这使我非常高兴,因为Redis变得更加灵活了。

我的错误/不完整的回答:

我非常确定这是不可能的。您不被允许使用全局变量(请阅读文档),而且脚本本身由Redis Lua引擎获得本地和临时范围。

Lua函数会自动设置一个“写入”标志,如果它们执行任何写入操作。这将启动一个事务。如果您级联Lua调用,则Redis中的簿记将变得非常麻烦,特别是当级联在Redis从服务器上执行时。这就是为什么EVALEVALSHA故意不作为有效的Redis调用在Lua脚本内部提供。同样适用于调用已经'加载'的Lua函数,这正是您试图做的。如果从服务器重新启动,第一个脚本的加载和第二个脚本的执行之间会发生什么?

我们如何克服这个限制:

不要使用EVAL,只使用SCRIPT LOADEVALSHA。 将SHA1存储在Redis哈希集合中。

我们在我们的版本控制系统中自动化了这个过程,所以提交的Lua脚本会自动在Redis主服务器上存储它的SHA1校验和,存储在一个逻辑名称的哈希集合中。客户端不能在从服务器上使用EVAL(我们在配置文件中禁用了EVAL+LOAD)。但是客户端可以要求下一步的SHA1。几乎所有我们的Lua函数都返回下一步的SHA1。

希望这可以帮助您,TW


我不太确定你所说的“但客户端可以要求下一步的SHA1”或“下一次调用”的意思。所以我的理解是,你不能在脚本内运行脚本,但你可以在脚本外使用EVALSHA?这有什么帮助呢? - Tim Fairbrother
它有助于运行一个集中测试的代码单元。这可能与您想要的不同,我不确定。我希望我已经清楚地表明了 Lua 脚本必须是“独立的”功能。这是设计上的考虑(在我看来相当美丽)。Redis Lua 不是作为代码库而存在的。 - Tw Bert
好的,谢谢。我有一些用于格式化响应的方法,并希望在其他脚本中重用它们。看起来我需要复制这些函数。 - Tim Fairbrother
不客气,而且那是正确的。从程序员的角度来看,这可能看起来不太“好”,但如果你从分布式事务NoSql的角度来看,它就有很多意义。而且它非常强大,你可以在整个网络中“从属”于你的逻辑。我可以推荐针对这些问题进行源代码控制自动化,如果它适合你的项目范围/规模的话。在通用的NoSql术语中:去规范化是非常常见的。在这里,你可以看到它也适用于源代码。 - Tw Bert

0
因为我不是一个满足于现状的人,所以我建立了一个包,允许简单的内部调用语义。这个包(适用于Python)可以在GitHub上找到。
简而言之,它使用ARGV作为调用堆栈,将KEYS/ARGV引用转换为_KEYS_ARGV,在内部使用Redis作为名称-哈希映射,并将CALL.<name>(<keys>, <argv>)转换为表追加+ Redis查找+ Lua函数调用。 METHOD.txt文件描述了发生的事情,我用来转换Lua脚本的所有正则表达式都可以在lua_call.py中找到。请随意重用我的语义。
函数注册表的使用使得它在Redis集群或任何其他多分片设置中工作的可能性非常小,但对于单主应用程序,它应该在可预见的未来内工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接