FUNCTION LOAD 是可运维的方案,因它将函数库持久化至AOF/RDB、主从同步且集群全局可见;SCRIPT LOAD 仅为连接级缓存,重启、主从切换、集群路由均导致失效。

因为 SCRIPT LOAD + EVALSHA 在生产环境根本不可靠——重启丢脚本、主从切换后报 NOSCRIPT No matching script、集群路由失败是常态,不是偶发问题;而 FUNCTION LOAD 把函数库当数据库状态持久化,AOF/RDB 落盘、主从自动同步、集群全局可见,这才是可运维的方案。
SCRIPT LOAD 脚本一重启就失效,不是配置问题,是设计如此
Redis 6.0 和 7.0 都明确不把 SCRIPT LOAD 的结果写入 RDB 或 AOF,也不复制到从节点。这意味着:
- 主节点重启后,所有已缓存的 SHA1 清空,
EVALSHA立即报错NOSCRIPT No matching script - 主从切换后,新主节点没有脚本缓存,客户端 fallback 到
EVAL,每次传完整脚本,网络开销翻倍 - 集群模式下,脚本只存在于执行时所在的 slot 节点,
EVALSHA可能因请求路由到无脚本的节点而失败
官方文档在 7.0 版本首次加粗强调:“用户必须自行管理脚本生命周期”。这不是 bug,是 SCRIPT LOAD 的定位:它只是连接级缓存,不是数据库对象。
FUNCTION LOAD 才真正把脚本“存进数据库”
FUNCTION LOAD 注册的函数库会真实写入 AOF、参与 RDB 快照,并自动复制到所有从节点——它被当成 Redis 数据库状态的一部分来对待:
- 重启后,
FCALL myfunc 0 key照常执行,无需客户端重载 - 故障转移后,新主节点已有函数定义,调用零中断
- 集群中函数按库名全局注册(非 per-slot),
FCALL自动路由到对应数据所在节点,不依赖脚本本地存在 -
FUNCTION LIST和FUNCTION STATS提供可观测性,能查执行次数、错误率、最后调用时间
迁移到 Function 容易踩的三个硬性门槛
不能直接把旧 Lua 脚本内容塞进 FUNCTION LOAD,运行时沙箱更严格:
- 第一行必须是
#!lua name=mylib,且不能有空行或注释,否则报user_script:1: unexpected symbol -
redis.call()的命令名必须全小写:redis.call("incr", key)合法,REDIS.CALL("INCR", key)报错 - 禁止访问
os、io、loadstring等非安全模块;不能用全局变量,所有状态必须通过keys/args显式传入;local counter = 0每次调用都是新实例 - 必须显式调用
redis.register_function('name', handler),否则FCALL找不到函数
FCALL 失败常见原因和排查路径
FCALL 错误比 EVAL 更结构化,但新手常卡在环境准备阶段:
- 没加
REPLACE参数更新同名库 → 报ERR Library already exists - 函数库名重复或含非法字符 →
FUNCTION LIST查不到,FCALL报ERR Unknown function - 参数个数或类型错位:比如
FCALL myrate 1 limit:uid 60中,60是ARGV[1],不是硬编码,漏传或传错位置会静默失败或逻辑异常 - 函数内修改了只读表(如直接赋值
num = unpack(args))→ 报ERR user_function:7: Attempt to modify a readonly table,必须写成local num = unpack(args)
最常被忽略的是:函数体里没做 exists 判断就直接 incr,导致 key 被创建为永不过期;这在旧 Lua 脚本里靠竞态规避,Function 里必须显式处理——原子性还在,但逻辑责任移交给了你。


















