redis 7.0 引入 functions 是为解决 eval/evalsha 持久化与复制不可靠问题;function load 脚本写入 aof/rdb、同步从节点、全局注册,而 eval 脚本重启即失、不复制、非全局,且 functions 运行仍阻塞主线程。

Redis 7.0 引入 Functions 不是为了“增强 Lua”,而是为了解决 EVAL/EVALSHA 在生产环境里根本不可靠的持久化与复制问题——脚本丢了不是小概率事件,是常态。
SCRIPT LOAD 加载的脚本为什么一重启就失效
Redis 6.0 和 7.0 都明确不把 SCRIPT LOAD 的结果写入 RDB 或 AOF,也不同步到从节点。这意味着:
- 主节点重启后,所有已缓存的 SHA1 值清空,
EVALSHA立即报错NOSCRIPT No matching script - 主从切换后,新主节点没有脚本缓存,客户端 fallback 到
EVAL,每次传完整脚本,网络开销翻倍 - 集群模式下,脚本只存在于执行时所在的 slot 节点,
EVALSHA可能因请求路由到无脚本的节点而失败
这不是配置没配好,是设计如此。官方文档在 7.0 版本首次把这条写进规范,并加粗强调:“用户必须自行管理脚本生命周期”。
FUNCTION LOAD 怎么做到“脚本不丢”
FUNCTION LOAD 注册的函数库会真实写入 AOF、参与 RDB 快照,并自动复制到所有从节点——它被当成 Redis 数据库状态的一部分来对待。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 重启后,
FCALL myfunc 0 key照常执行,无需客户端重载 - 故障转移后,新主节点已有函数定义,调用零中断
- 集群中函数按库名全局注册(非 per-slot),
FCALL自动路由到对应数据所在节点,不依赖脚本本地存在 -
FUNCTION LIST和FUNCTION STATS提供可观测性,能查执行次数、错误率、最后调用时间
迁移 EVAL 脚本到 Functions 容易踩的坑
直接把旧 EVAL 脚本内容塞进 FUNCTION LOAD 会失败,不是语法问题,是运行时沙箱收紧:
- 第一行必须是
#!lua name=mylib,且不能有空行或注释;否则报user_script:1: unexpected symbol - 所有
redis.call()参数必须全小写命令名,redis.call("INCR", key)合法,redis.call("incr", key)也合法,但REDIS.CALL("INCR", key)报错 - 禁止访问
os、io、loadstring等非安全模块;math.random()可用,os.time()不可用 - 不能用全局变量,所有状态必须通过
keys/args显式传入;local counter = 0每次调用都是新实例
Functions 不是万能的,关键限制得盯住
它解决了持久化和复制问题,但没解决原子性执行带来的阻塞风险:
- 函数仍运行在 Redis 单线程中,一个慢函数(比如循环 10 万次、调用
KEYS *)会让整个实例卡住 - 不支持热更新单个函数:改一行就得
FUNCTION DELETE整个库再FUNCTION LOAD全量重载 - ACL 权限需显式授权,比如
+function|call和~mylib.*,旧版 ACL 规则默认不放行FCALL - 目前仅支持 Lua(因内建解释器),所谓“多语言支持”仍是未来规划,别被文档误导
真正容易被忽略的是:Functions 的“可靠”只针对函数定义本身;一旦函数内部逻辑出错(比如 redis.call("GET", nil)),错误仍会透出,且无法像应用层那样加重试或降级——它要么成功,要么失败,没有中间态。










